/**
 * Verifier Manager
 *
 * Loads verifier configurations and orchestrates verification execution.
 */

import { promises as fs } from 'fs';
import path from 'path';
import type { LanguageModel } from 'ai';
import {
  VerifiersConfigSchema,
  type VerifiersConfig,
  type VerifierConfig,
  type VerifierResult,
  type VerificationReport,
  type VerificationContext,
  createReport,
} from './types.js';
import { runCommandVerifier } from './command.js';
import { runLLMVerifier } from './llm.js';
import { createLogger, type Logger } from '../utils/logger.js';

/**
 * Default config file path relative to repo root
 */
const DEFAULT_CONFIG_PATH = '.emdash/verifiers.json';

/**
 * Options for VerifierManager
 */
export interface VerifierManagerOptions {
  /** Repository root directory */
  repoRoot?: string;
  /** Custom config file path */
  configPath?: string;
  /** LLM model for LLM verifiers */
  model?: LanguageModel;
  /** Logger instance */
  logger?: Logger;
}

/**
 * Verifier Manager
 *
 * Manages loading and running verifiers:
 * - Loads config from .emdash/verifiers.json
 * - Runs command and LLM verifiers
 * - Returns aggregated verification reports
 */
export class VerifierManager {
  private repoRoot: string;
  private configPath: string;
  private model?: LanguageModel;
  private logger: Logger;
  private config?: VerifiersConfig;

  constructor(options?: VerifierManagerOptions) {
    this.repoRoot = options?.repoRoot ?? process.cwd();
    this.configPath = options?.configPath ?? path.join(this.repoRoot, DEFAULT_CONFIG_PATH);
    this.model = options?.model;
    this.logger = options?.logger ?? createLogger({ name: 'verifier-manager' });
  }

  /**
   * Load configuration from file
   */
  async loadConfig(): Promise<VerifiersConfig> {
    if (this.config) return this.config;

    try {
      const content = await fs.readFile(this.configPath, 'utf-8');
      const parsed = JSON.parse(content);

      // Handle legacy maxRetries field
      if ('maxRetries' in parsed && !('maxAttempts' in parsed)) {
        parsed.maxAttempts = parsed.maxRetries;
      }

      const validated = VerifiersConfigSchema.parse(parsed);
      this.config = validated;
      this.logger.debug({ verifierCount: validated.verifiers.length }, 'Loaded verifier config');
      return validated;
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
        this.logger.debug('No verifier config found, using defaults');
        this.config = { verifiers: [], maxAttempts: 3 };
        return this.config;
      }
      this.logger.error({ error }, 'Failed to load verifier config');
      throw error;
    }
  }

  /**
   * Get enabled verifiers
   */
  async getEnabledVerifiers(): Promise<VerifierConfig[]> {
    const config = await this.loadConfig();
    return config.verifiers.filter((v) => v.enabled);
  }

  /**
   * Get max attempts from config
   */
  async getMaxAttempts(): Promise<number> {
    const config = await this.loadConfig();
    return config.maxAttempts;
  }

  /**
   * Run all enabled verifiers
   */
  async runAll(context?: VerificationContext): Promise<VerificationReport> {
    const verifiers = await this.getEnabledVerifiers();

    if (verifiers.length === 0) {
      this.logger.info('No verifiers configured');
      return createReport([]);
    }

    this.logger.info({ count: verifiers.length }, 'Running verifiers');

    const ctx: VerificationContext = {
      cwd: this.repoRoot,
      ...context,
    };

    const results: VerifierResult[] = [];

    // Run verifiers sequentially
    for (const verifier of verifiers) {
      const result = await this.runVerifier(verifier, ctx);
      results.push(result);
    }

    const report = createReport(results);
    this.logger.info(
      { passed: report.passedCount, failed: report.failedCount, duration: report.totalDuration },
      report.summary
    );

    return report;
  }

  /**
   * Run a single verifier
   */
  async runVerifier(config: VerifierConfig, context: VerificationContext): Promise<VerifierResult> {
    this.logger.debug({ name: config.name, type: config.type }, 'Running verifier');

    if (config.type === 'command') {
      return runCommandVerifier(config, context, { logger: this.logger });
    }

    if (config.type === 'llm') {
      if (!this.model) {
        return {
          name: config.name,
          passed: false,
          output: '',
          duration: 0,
          issues: [{ message: 'No LLM model configured for LLM verifier', severity: 'error' }],
          error: 'No LLM model configured',
        };
      }
      return runLLMVerifier(config, context, { model: this.model, logger: this.logger });
    }

    return {
      name: config.name,
      passed: false,
      output: '',
      duration: 0,
      issues: [{ message: `Unknown verifier type: ${config.type}`, severity: 'error' }],
      error: `Unknown verifier type: ${config.type}`,
    };
  }

  /**
   * Run specific verifiers by name
   */
  async runByNames(names: string[], context?: VerificationContext): Promise<VerificationReport> {
    const allVerifiers = await this.getEnabledVerifiers();
    const verifiers = allVerifiers.filter((v) => names.includes(v.name));

    if (verifiers.length === 0) {
      this.logger.warn({ names }, 'No matching verifiers found');
      return createReport([]);
    }

    const ctx: VerificationContext = {
      cwd: this.repoRoot,
      ...context,
    };

    const results: VerifierResult[] = [];
    for (const verifier of verifiers) {
      const result = await this.runVerifier(verifier, ctx);
      results.push(result);
    }

    return createReport(results);
  }

  /**
   * Save configuration to file
   */
  async saveConfig(config: VerifiersConfig): Promise<void> {
    // Ensure directory exists
    const dir = path.dirname(this.configPath);
    await fs.mkdir(dir, { recursive: true });

    // Write config
    const content = JSON.stringify(config, null, 2);
    await fs.writeFile(this.configPath, content, 'utf-8');

    // Update cached config
    this.config = config;
    this.logger.debug('Saved verifier config');
  }

  /**
   * Add a verifier to the configuration
   */
  async addVerifier(verifier: VerifierConfig): Promise<void> {
    const config = await this.loadConfig();

    // Check for duplicate name
    if (config.verifiers.some((v) => v.name === verifier.name)) {
      throw new Error(`Verifier with name "${verifier.name}" already exists`);
    }

    config.verifiers.push(verifier);
    await this.saveConfig(config);
    this.logger.info({ name: verifier.name }, 'Added verifier');
  }

  /**
   * Remove a verifier from the configuration
   */
  async removeVerifier(name: string): Promise<boolean> {
    const config = await this.loadConfig();
    const initialLength = config.verifiers.length;
    config.verifiers = config.verifiers.filter((v) => v.name !== name);

    if (config.verifiers.length === initialLength) {
      return false;
    }

    await this.saveConfig(config);
    this.logger.info({ name }, 'Removed verifier');
    return true;
  }

  /**
   * Enable/disable a verifier
   */
  async setEnabled(name: string, enabled: boolean): Promise<boolean> {
    const config = await this.loadConfig();
    const verifier = config.verifiers.find((v) => v.name === name);

    if (!verifier) {
      return false;
    }

    verifier.enabled = enabled;
    await this.saveConfig(config);
    this.logger.info({ name, enabled }, 'Updated verifier enabled status');
    return true;
  }

  /**
   * Set the LLM model for LLM verifiers
   */
  setModel(model: LanguageModel): void {
    this.model = model;
  }

  /**
   * Clear cached configuration
   */
  clearCache(): void {
    this.config = undefined;
  }
}

/**
 * Create a verifier manager for a repository
 */
export function createVerifierManager(options?: VerifierManagerOptions): VerifierManager {
  return new VerifierManager(options);
}
