// @lifecycle canonical - File service for methodology YAML read-merge-write operations.
/**
 * Methodology File Service
 *
 * Provides read-merge-write pattern for methodology YAML files.
 * Ensures updates are additive rather than destructive.
 */

import { existsSync } from 'fs';
import { mkdir, readFile } from 'node:fs/promises';
import { join } from 'path';

import {
  ResourceMutationTransaction,
  ResourceVerificationService,
} from '../../../../modules/resources/services/index.js';
import { safeWriteFile } from '../../../../shared/utils/file-transactions.js';
import { loadYamlFile } from '../../../../shared/utils/yaml/yaml-file-loader.js';
import { serializeYaml } from '../../../../shared/utils/yaml/yaml-parser.js';

import type { ConfigManager, Logger } from '../../../../shared/types/index.js';
import type { MethodologyCreationData } from '../core/types.js';

// ============================================================================
// Types
// ============================================================================

export interface MethodologyFileWriterDependencies {
  logger: Logger;
  configManager: ConfigManager;
  resourceVerificationService?: ResourceVerificationService;
  resourceMutationTransaction?: ResourceMutationTransaction;
}

export interface ExistingMethodologyData {
  methodology: Record<string, unknown>;
  phases: Record<string, unknown> | null;
  systemPrompt: string | null;
  judgePrompt: string | null;
  methodologyPath: string;
  phasesPath: string | null;
  systemPromptPath: string;
  judgePromptPath: string | null;
}

export interface MethodologyFileResult {
  success: boolean;
  paths?: string[];
  error?: string;
}

// ============================================================================
// Service Implementation
// ============================================================================

export class MethodologyFileWriter {
  private logger: Logger;
  private configManager: ConfigManager;
  private readonly verificationService: ResourceVerificationService;
  private readonly mutationTransaction: ResourceMutationTransaction;

  constructor(deps: MethodologyFileWriterDependencies) {
    this.logger = deps.logger;
    this.configManager = deps.configManager;
    this.verificationService =
      deps.resourceVerificationService ?? new ResourceVerificationService();
    this.mutationTransaction =
      deps.resourceMutationTransaction ?? new ResourceMutationTransaction();
  }

  // ==========================================================================
  // Public API
  // ==========================================================================

  /**
   * Check if a methodology exists on the filesystem
   *
   * @param id - Methodology identifier
   * @returns true if methodology.yaml exists for this ID
   */
  methodologyExists(id: string): boolean {
    const methodologyDir = this.getMethodologyDir(id);
    const methodologyPath = join(methodologyDir, 'methodology.yaml');
    return existsSync(methodologyPath);
  }

  /**
   * Delete a methodology directory from the filesystem
   *
   * @param id - Methodology identifier
   * @returns true if deletion succeeded
   */
  async deleteMethodology(id: string): Promise<boolean> {
    const methodologyDir = this.getMethodologyDir(id);

    if (!existsSync(methodologyDir)) {
      return false;
    }

    try {
      const { rm } = await import('fs/promises');
      await rm(methodologyDir, { recursive: true });
      this.logger.debug(`Deleted methodology directory: ${methodologyDir}`);
      return true;
    } catch (error) {
      this.logger.error(`Failed to delete methodology '${id}':`, error);
      return false;
    }
  }

  /**
   * Load existing methodology files from disk
   */
  async loadExistingMethodology(id: string): Promise<ExistingMethodologyData | null> {
    const methodologyDir = this.getMethodologyDir(id);
    const methodologyPath = join(methodologyDir, 'methodology.yaml');

    if (!existsSync(methodologyPath)) {
      return null;
    }

    try {
      const methodology = await loadYamlFile<Record<string, unknown>>(methodologyPath);
      if (methodology === undefined) {
        this.logger.error(`Failed to parse methodology.yaml for ${id}`);
        return null;
      }

      // Load phases.yaml if referenced
      let phases: Record<string, unknown> | null = null;
      let phasesPath: string | null = null;
      const phasesFileRef = methodology['phasesFile'];
      if (phasesFileRef !== undefined && phasesFileRef !== null) {
        phasesPath = join(methodologyDir, String(phasesFileRef));
        if (existsSync(phasesPath)) {
          const loadedPhases = await loadYamlFile<Record<string, unknown>>(phasesPath);
          phases = loadedPhases ?? null;
        }
      }

      // Load system-prompt.md
      const systemPromptPath = join(methodologyDir, 'system-prompt.md');
      let systemPrompt: string | null = null;
      if (existsSync(systemPromptPath)) {
        systemPrompt = await readFile(systemPromptPath, 'utf8');
      }

      // Load judge-prompt.md if referenced
      let judgePrompt: string | null = null;
      let judgePromptPath: string | null = null;
      const judgePromptFileRef = methodology['judgePromptFile'];
      if (judgePromptFileRef !== undefined && judgePromptFileRef !== null) {
        judgePromptPath = join(methodologyDir, String(judgePromptFileRef));
        if (existsSync(judgePromptPath)) {
          judgePrompt = await readFile(judgePromptPath, 'utf8');
        }
      }

      return {
        methodology,
        phases,
        systemPrompt,
        judgePrompt,
        methodologyPath,
        phasesPath,
        systemPromptPath,
        judgePromptPath,
      };
    } catch (error) {
      this.logger.error(`Error loading methodology ${id}:`, error);
      return null;
    }
  }

  /**
   * Convert raw ExistingMethodologyData to typed MethodologyCreationData.
   * Extracts and maps fields from YAML structure to the typed interface.
   *
   * @param id - Methodology identifier
   * @param existing - Raw methodology data loaded from disk
   * @returns Typed MethodologyCreationData or null if essential fields missing
   */
  toMethodologyCreationData(
    id: string,
    existing: ExistingMethodologyData
  ): MethodologyCreationData | null {
    const { methodology, phases, systemPrompt } = existing;

    // Extract required fields from raw YAML (use bracket notation for Record<string, unknown>)
    const rawName = methodology['name'];
    const rawSystemGuidance = methodology['system_prompt_guidance'];
    const name = typeof rawName === 'string' ? rawName : undefined;
    const systemGuidance =
      systemPrompt ?? (typeof rawSystemGuidance === 'string' ? rawSystemGuidance : undefined);

    if (name === undefined || systemGuidance === undefined) {
      this.logger.debug(`Methodology '${id}' missing required fields for completeness check`);
      return null;
    }

    // Build typed creation data from raw YAML fields
    const rawMethodology = methodology['methodology'];
    const data: MethodologyCreationData = {
      id,
      name,
      methodology: typeof rawMethodology === 'string' ? rawMethodology : id.toUpperCase(),
      system_prompt_guidance: systemGuidance,
    };

    // Map optional fields from methodology.yaml (use bracket notation)
    const rawDescription = methodology['description'];
    const rawType = methodology['type'];
    const rawEnabled = methodology['enabled'];
    const rawGates = methodology['gates'];
    const rawToolDescriptions = methodology['tool_descriptions'];

    if (typeof rawDescription === 'string') data.description = rawDescription;
    if (typeof rawType === 'string') data.type = rawType;
    if (typeof rawEnabled === 'boolean') data.enabled = rawEnabled;
    if (rawGates !== undefined && rawGates !== null) {
      data.gates = rawGates as NonNullable<MethodologyCreationData['gates']>;
    }
    if (rawToolDescriptions !== undefined && rawToolDescriptions !== null) {
      data.tool_descriptions = rawToolDescriptions as NonNullable<
        MethodologyCreationData['tool_descriptions']
      >;
    }

    // Map phases-related fields (may come from phases.yaml or methodology.yaml)
    // Note: YAML uses camelCase (methodologyGates), but also check snake_case for legacy support
    const phasesSource = phases ?? methodology;
    const rawPhases = phasesSource['phases'];
    const rawMethodologyGates =
      methodology['methodologyGates'] ?? phasesSource['methodology_gates'];
    const rawProcessingSteps = phasesSource['processingSteps'] ?? phasesSource['processing_steps'];
    const rawExecutionSteps = phasesSource['executionSteps'] ?? phasesSource['execution_steps'];
    const rawQualityIndicators =
      phasesSource['qualityIndicators'] ?? phasesSource['quality_indicators'];
    const rawTemplateEnhancements =
      phasesSource['templateEnhancements'] ?? phasesSource['template_enhancements'];
    const rawExecutionFlow = phasesSource['executionFlow'] ?? phasesSource['execution_flow'];
    const rawMethodologyElements =
      methodology['methodologyElements'] ?? phasesSource['methodology_elements'];
    const rawArgumentSuggestions =
      methodology['argumentSuggestions'] ?? phasesSource['argument_suggestions'];
    const rawTemplateSuggestions =
      methodology['templateSuggestions'] ?? phasesSource['template_suggestions'];

    if (Array.isArray(rawPhases)) {
      data.phases = rawPhases as NonNullable<MethodologyCreationData['phases']>;
    }
    if (Array.isArray(rawMethodologyGates)) {
      data.methodology_gates = rawMethodologyGates as NonNullable<
        MethodologyCreationData['methodology_gates']
      >;
    }
    if (Array.isArray(rawProcessingSteps)) {
      data.processing_steps = rawProcessingSteps as NonNullable<
        MethodologyCreationData['processing_steps']
      >;
    }
    if (Array.isArray(rawExecutionSteps)) {
      data.execution_steps = rawExecutionSteps as NonNullable<
        MethodologyCreationData['execution_steps']
      >;
    }
    if (rawQualityIndicators !== undefined && rawQualityIndicators !== null) {
      data.quality_indicators = rawQualityIndicators as NonNullable<
        MethodologyCreationData['quality_indicators']
      >;
    }
    if (rawTemplateEnhancements !== undefined && rawTemplateEnhancements !== null) {
      data.template_enhancements = rawTemplateEnhancements as NonNullable<
        MethodologyCreationData['template_enhancements']
      >;
    }
    if (rawExecutionFlow !== undefined && rawExecutionFlow !== null) {
      data.execution_flow = rawExecutionFlow as NonNullable<
        MethodologyCreationData['execution_flow']
      >;
    }
    if (rawMethodologyElements !== undefined && rawMethodologyElements !== null) {
      data.methodology_elements = rawMethodologyElements as NonNullable<
        MethodologyCreationData['methodology_elements']
      >;
    }
    if (Array.isArray(rawArgumentSuggestions)) {
      data.argument_suggestions = rawArgumentSuggestions as NonNullable<
        MethodologyCreationData['argument_suggestions']
      >;
    }
    if (Array.isArray(rawTemplateSuggestions)) {
      data.template_suggestions = rawTemplateSuggestions as NonNullable<
        MethodologyCreationData['template_suggestions']
      >;
    }

    return data;
  }

  /**
   * Write methodology files with optional merge from existing data
   * @param data - Methodology data (can be partial for updates)
   * @param existingData - Existing methodology data to merge with (null for create)
   */
  async writeMethodologyFiles(
    data: Partial<MethodologyCreationData> & { id: string },
    existingData?: ExistingMethodologyData | null
  ): Promise<MethodologyFileResult> {
    const methodologyDir = this.getMethodologyDir(data.id);
    const methodologyYamlPath = join(methodologyDir, 'methodology.yaml');

    const txResult = await this.mutationTransaction.run({
      targets: [{ path: methodologyDir, kind: 'directory' }],
      mutate: async () => {
        const paths: string[] = [];

        await mkdir(methodologyDir, { recursive: true });
        paths.push(methodologyDir);

        // Build and merge methodology.yaml
        const newMethodologyData = this.buildMethodologyYamlData(data);
        const finalMethodologyData =
          existingData !== undefined && existingData !== null
            ? this.deepMerge(existingData.methodology, newMethodologyData)
            : newMethodologyData;

        const methodologyContent = serializeYaml(finalMethodologyData, { sortKeys: false });
        await safeWriteFile(methodologyYamlPath, methodologyContent);
        paths.push(methodologyYamlPath);

        // Handle phases.yaml
        const existingPhases = existingData?.phases ?? null;
        const needsPhasesFile = this.needsPhasesFile(data) || existingPhases !== null;
        if (needsPhasesFile) {
          const newPhasesData = this.buildPhasesYamlData(data);
          const hasNewPhasesData = Object.keys(newPhasesData).length > 0;
          const finalPhasesData =
            existingPhases !== null && hasNewPhasesData
              ? this.deepMerge(existingPhases, newPhasesData)
              : (existingPhases ?? newPhasesData);

          if (Object.keys(finalPhasesData).length > 0) {
            const phasesPath = join(methodologyDir, 'phases.yaml');
            const phasesContent = serializeYaml(finalPhasesData, { sortKeys: false });
            await safeWriteFile(phasesPath, phasesContent);
            paths.push(phasesPath);
          }
        }

        // Handle system-prompt.md
        const systemPromptPath = join(methodologyDir, 'system-prompt.md');
        const systemPromptContent = data.system_prompt_guidance ?? existingData?.systemPrompt ?? '';
        if (systemPromptContent !== '') {
          await safeWriteFile(systemPromptPath, systemPromptContent);
          paths.push(systemPromptPath);
        }

        // Handle judge-prompt.md
        const existingJudgePrompt = existingData?.judgePrompt ?? null;
        const hasJudgePrompt = data.judge_prompt !== undefined || existingJudgePrompt !== null;
        if (hasJudgePrompt) {
          const judgePromptPath = join(methodologyDir, 'judge-prompt.md');
          const judgePromptContent = data.judge_prompt ?? existingJudgePrompt ?? '';
          if (judgePromptContent !== '') {
            await safeWriteFile(judgePromptPath, judgePromptContent);
            paths.push(judgePromptPath);
          }
        }

        return { paths };
      },
      validate: () =>
        this.verificationService.validateFile('methodologies', data.id, methodologyYamlPath),
    });

    if (!txResult.success) {
      return {
        success: false,
        error: txResult.rolledBack
          ? `Methodology write failed and was rolled back: ${txResult.error}`
          : `Methodology write failed: ${txResult.error}`,
      };
    }

    return { success: true, paths: txResult.result?.paths ?? [] };
  }

  // ==========================================================================
  // YAML Data Builders
  // ==========================================================================

  /**
   * Build methodology.yaml data from input (only sets defined fields)
   */
  buildMethodologyYamlData(
    data: Partial<MethodologyCreationData> & { id: string }
  ): Record<string, unknown> {
    const yamlData: Record<string, unknown> = {};
    const typeValue = data.type ?? data.methodology;

    // Core fields - id is always required
    yamlData['id'] = data.id.toLowerCase();

    // Only set name if provided (for partial updates)
    if (data.name !== undefined) {
      yamlData['name'] = data.name;
    }

    // Type/methodology (both for backward compat) - only if provided
    if (typeValue !== undefined) {
      yamlData['type'] = typeValue;
      yamlData['methodology'] = typeValue;
    }

    // Enabled defaults to true
    yamlData['enabled'] = data.enabled ?? true;

    // System prompt guidance
    if (data.system_prompt_guidance !== undefined) {
      yamlData['systemPromptGuidance'] = data.system_prompt_guidance;
    }

    // Check if phases.yaml is needed
    if (this.needsPhasesFile(data)) {
      yamlData['phasesFile'] = 'phases.yaml';
    }

    // Optional fields (only if defined)
    if (data.gates !== undefined) {
      yamlData['gates'] = data.gates;
    }
    if (data.tool_descriptions !== undefined) {
      yamlData['toolDescriptions'] = data.tool_descriptions;
    }

    // Advanced methodology fields (only if defined and non-empty)
    if (data.methodology_gates !== undefined && data.methodology_gates.length > 0) {
      yamlData['methodologyGates'] = data.methodology_gates;
    }
    if (data.template_suggestions !== undefined && data.template_suggestions.length > 0) {
      yamlData['templateSuggestions'] = data.template_suggestions;
    }
    if (data.methodology_elements !== undefined) {
      yamlData['methodologyElements'] = data.methodology_elements;
    }
    if (data.argument_suggestions !== undefined && data.argument_suggestions.length > 0) {
      yamlData['argumentSuggestions'] = data.argument_suggestions;
    }
    if (data.judge_prompt !== undefined) {
      yamlData['judgePromptFile'] = 'judge-prompt.md';
    }

    // Always set version for new methodologies
    yamlData['version'] ??= '1.0.0';

    return yamlData;
  }

  /**
   * Build phases.yaml data from input (only sets defined fields)
   */
  buildPhasesYamlData(data: Partial<MethodologyCreationData>): Record<string, unknown> {
    const phasesData: Record<string, unknown> = {};

    if (data.phases !== undefined && data.phases.length > 0) {
      phasesData['phases'] = data.phases;
    }
    if (data.processing_steps !== undefined && data.processing_steps.length > 0) {
      phasesData['processingSteps'] = data.processing_steps;
    }
    if (data.execution_steps !== undefined && data.execution_steps.length > 0) {
      phasesData['executionSteps'] = data.execution_steps;
    }
    if (data.execution_type_enhancements !== undefined) {
      phasesData['executionTypeEnhancements'] = data.execution_type_enhancements;
    }
    if (data.template_enhancements !== undefined) {
      phasesData['templateEnhancements'] = data.template_enhancements;
    }
    if (data.execution_flow !== undefined) {
      phasesData['executionFlow'] = data.execution_flow;
    }
    if (data.quality_indicators !== undefined) {
      phasesData['qualityIndicators'] = data.quality_indicators;
    }

    return phasesData;
  }

  // ==========================================================================
  // Helpers
  // ==========================================================================

  /**
   * Get the directory path for a methodology.
   * Used by versioning service to locate history files.
   */
  public getMethodologyDir(id: string): string {
    const serverRoot = this.configManager.getServerRoot();
    return join(serverRoot, 'resources', 'methodologies', id.toLowerCase());
  }

  private needsPhasesFile(data: Partial<MethodologyCreationData>): boolean {
    return (
      (data.phases !== undefined && data.phases.length > 0) ||
      data.processing_steps !== undefined ||
      data.execution_steps !== undefined ||
      data.execution_type_enhancements !== undefined ||
      data.template_enhancements !== undefined ||
      data.execution_flow !== undefined ||
      data.quality_indicators !== undefined
    );
  }

  /**
   * Deep-merge source into target
   * - Arrays: replaced (not merged)
   * - Objects: recursively merged
   * - undefined: skipped (preserves target value)
   */
  private deepMerge(
    target: Record<string, unknown>,
    source: Record<string, unknown>
  ): Record<string, unknown> {
    const result = { ...target };

    for (const key of Object.keys(source)) {
      const sourceValue = source[key];
      const targetValue = target[key];

      // Skip undefined (don't overwrite with nothing)
      if (sourceValue === undefined) {
        continue;
      }

      // Recursive merge for plain objects
      if (this.isPlainObject(sourceValue) && this.isPlainObject(targetValue)) {
        result[key] = this.deepMerge(targetValue, sourceValue);
      } else {
        // Replace for arrays, primitives, nulls
        result[key] = sourceValue;
      }
    }

    return result;
  }

  private isPlainObject(value: unknown): value is Record<string, unknown> {
    return (
      typeof value === 'object' &&
      value !== null &&
      !Array.isArray(value) &&
      Object.getPrototypeOf(value) === Object.prototype
    );
  }
}
