import { readFileSync, existsSync, statSync, copyFileSync } from 'node:fs';
import { join } from 'node:path';

/**
 * Framework-contract files that `syncFramework` seeds from `framework/defaults/`
 * into the mosaic home root on first install. These are the only files the
 * wizard is allowed to touch as a one-time seed — SOUL.md and USER.md are
 * generated from templates by their respective wizard stages with
 * user-supplied values, and anything else under `defaults/` (README.md,
 * audit snapshots, etc.) is framework-internal and must not leak into the
 * user's mosaic home.
 *
 * This list must match the explicit seed loop in
 * packages/mosaic/framework/install.sh.
 */
// Framework-owned contract files: re-copied from defaults/ on every upgrade (a
// divergent existing copy is backed up once to <file>.pre-constitution.bak first).
// MUST match FRAMEWORK_OWNED in packages/mosaic/framework/install.sh (append-friendly).
export const FRAMEWORK_OWNED_FILES = ['CONSTITUTION.md', 'AGENTS.md', 'STANDARDS.md'] as const;

// User-seeded contract files: written once on first install, then owned by the user.
// MUST match USER_SEEDED in packages/mosaic/framework/install.sh.
export const USER_SEEDED_FILES = ['TOOLS.md'] as const;

// Union, retained for callers/tests that assert the full seed set on a fresh install.
export const DEFAULT_SEED_FILES = [...FRAMEWORK_OWNED_FILES, ...USER_SEEDED_FILES] as const;
import type { ConfigService, ConfigSection, ResolvedConfig } from './config-service.js';
import type { SoulConfig, UserConfig, ToolsConfig, InstallAction } from '../types.js';
import { soulSchema, userSchema, toolsSchema } from './schemas.js';
import { renderTemplate } from '../template/engine.js';
import {
  buildSoulTemplateVars,
  buildUserTemplateVars,
  buildToolsTemplateVars,
} from '../template/builders.js';
import { atomicWrite, backupFile, syncDirectory } from '../platform/file-ops.js';

/**
 * Parse a SoulConfig from an existing SOUL.md file.
 */
function parseSoulFromMarkdown(content: string): SoulConfig {
  const config: SoulConfig = {};

  const nameMatch = content.match(/You are \*\*(.+?)\*\*/);
  if (nameMatch?.[1]) config.agentName = nameMatch[1];

  const roleMatch = content.match(/Role identity: (.+)/);
  if (roleMatch?.[1]) config.roleDescription = roleMatch[1];

  if (content.includes('Be direct, concise')) {
    config.communicationStyle = 'direct';
  } else if (content.includes('Be warm and conversational')) {
    config.communicationStyle = 'friendly';
  } else if (content.includes('Use professional, structured')) {
    config.communicationStyle = 'formal';
  }

  return config;
}

/**
 * Parse a UserConfig from an existing USER.md file.
 */
function parseUserFromMarkdown(content: string): UserConfig {
  const config: UserConfig = {};

  const nameMatch = content.match(/\*\*Name:\*\* (.+)/);
  if (nameMatch?.[1]) config.userName = nameMatch[1];

  const pronounsMatch = content.match(/\*\*Pronouns:\*\* (.+)/);
  if (pronounsMatch?.[1]) config.pronouns = pronounsMatch[1];

  const tzMatch = content.match(/\*\*Timezone:\*\* (.+)/);
  if (tzMatch?.[1]) config.timezone = tzMatch[1];

  return config;
}

/**
 * Parse a ToolsConfig from an existing TOOLS.md file.
 */
function parseToolsFromMarkdown(content: string): ToolsConfig {
  const config: ToolsConfig = {};

  const credsMatch = content.match(/\*\*Location:\*\* (.+)/);
  if (credsMatch?.[1]) config.credentialsLocation = credsMatch[1];

  return config;
}

export class FileConfigAdapter implements ConfigService {
  constructor(
    private mosaicHome: string,
    private sourceDir: string,
  ) {}

  async readSoul(): Promise<SoulConfig> {
    const path = join(this.mosaicHome, 'SOUL.md');
    if (!existsSync(path)) return {};
    return parseSoulFromMarkdown(readFileSync(path, 'utf-8'));
  }

  async readUser(): Promise<UserConfig> {
    const path = join(this.mosaicHome, 'USER.md');
    if (!existsSync(path)) return {};
    return parseUserFromMarkdown(readFileSync(path, 'utf-8'));
  }

  async readTools(): Promise<ToolsConfig> {
    const path = join(this.mosaicHome, 'TOOLS.md');
    if (!existsSync(path)) return {};
    return parseToolsFromMarkdown(readFileSync(path, 'utf-8'));
  }

  async writeSoul(config: SoulConfig): Promise<void> {
    const validated = soulSchema.parse(config);
    const templatePath = this.findTemplate('SOUL.md.template');
    if (!templatePath) return;

    const template = readFileSync(templatePath, 'utf-8');
    const vars = buildSoulTemplateVars(validated);
    const output = renderTemplate(template, vars);

    const outPath = join(this.mosaicHome, 'SOUL.md');
    backupFile(outPath);
    atomicWrite(outPath, output);
  }

  async writeUser(config: UserConfig): Promise<void> {
    const validated = userSchema.parse(config);
    const templatePath = this.findTemplate('USER.md.template');
    if (!templatePath) return;

    const template = readFileSync(templatePath, 'utf-8');
    const vars = buildUserTemplateVars(validated);
    const output = renderTemplate(template, vars);

    const outPath = join(this.mosaicHome, 'USER.md');
    backupFile(outPath);
    atomicWrite(outPath, output);
  }

  async writeTools(config: ToolsConfig): Promise<void> {
    const validated = toolsSchema.parse(config);
    const templatePath = this.findTemplate('TOOLS.md.template');
    if (!templatePath) return;

    const template = readFileSync(templatePath, 'utf-8');
    const vars = buildToolsTemplateVars(validated);
    const output = renderTemplate(template, vars);

    const outPath = join(this.mosaicHome, 'TOOLS.md');
    backupFile(outPath);
    atomicWrite(outPath, output);
  }

  async syncFramework(action: InstallAction): Promise<void> {
    // Must match PRESERVE_PATHS in packages/mosaic/framework/install.sh so
    // the bash and TS install paths have the same upgrade-preservation
    // semantics. Contract files (AGENTS.md, STANDARDS.md, TOOLS.md) are
    // seeded from defaults/ on first install and preserved thereafter;
    // identity files (SOUL.md, USER.md) are generated by wizard stages and
    // must never be touched by the framework sync.
    const preservePaths =
      action === 'keep' || action === 'reconfigure'
        ? [
            'CONSTITUTION.md',
            'AGENTS.md',
            'SOUL.md',
            'USER.md',
            'TOOLS.md',
            'STANDARDS.md',
            'memory',
            'sources',
            'credentials',
            // User-authored fleet data MUST survive `mosaic update`'s re-seed.
            // The framework seeds only fleet/examples + fleet/roles +
            // fleet/roster.schema.json; the operator's roster, per-agent env, and
            // heartbeat run dir stay user-owned. (Mirror of install.sh PRESERVE_PATHS.)
            'fleet/*.yaml',
            'fleet/agents',
            'fleet/run',
          ]
        : [];

    syncDirectory(this.sourceDir, this.mosaicHome, {
      preserve: preservePaths,
      excludeGit: true,
    });

    // Reconcile framework-contract files from framework/defaults/ into the mosaic
    // home root: framework-owned files (CONSTITUTION/AGENTS/STANDARDS) are overwritten
    // every upgrade (backup-once); user-seeded files (TOOLS) are written on first
    // install only. Mirrors reconcile_framework_files() in install.sh.
    //
    // SOUL.md and USER.md are deliberately NOT seeded here. They are
    // generated from templates by the soul/user wizard stages with
    // user-supplied values; seeding them from defaults would clobber the
    // identity flow and leak placeholder content into the mosaic home.
    const defaultsDir = join(this.sourceDir, 'defaults');
    if (existsSync(defaultsDir)) {
      // Framework-owned: overwrite from defaults/ every sync; back up a divergent
      // existing copy ONCE to <file>.pre-constitution.bak before the first overwrite.
      for (const entry of FRAMEWORK_OWNED_FILES) {
        const src = join(defaultsDir, entry);
        const dest = join(this.mosaicHome, entry);
        if (!existsSync(src) || !statSync(src).isFile()) continue;
        // Already current — skip to avoid mtime churn.
        if (existsSync(dest) && readFileSync(src).equals(readFileSync(dest))) continue;
        const bak = `${dest}.pre-constitution.bak`;
        if (existsSync(dest) && !existsSync(bak)) {
          copyFileSync(dest, bak);
        }
        copyFileSync(src, dest);
      }
      // User-seeded: write only if absent.
      for (const entry of USER_SEEDED_FILES) {
        const src = join(defaultsDir, entry);
        const dest = join(this.mosaicHome, entry);
        if (existsSync(dest)) continue;
        if (!existsSync(src) || !statSync(src).isFile()) continue;
        copyFileSync(src, dest);
      }
    }
  }

  async readAll(): Promise<ResolvedConfig> {
    const [soul, user, tools] = await Promise.all([
      this.readSoul(),
      this.readUser(),
      this.readTools(),
    ]);
    return { soul, user, tools };
  }

  async getValue(dottedKey: string): Promise<unknown> {
    const parts = dottedKey.split('.');
    const section = parts[0] ?? '';
    const field = parts.slice(1).join('.');
    const config = await this.readAll();
    if (!this.isValidSection(section)) return undefined;
    const sectionData = config[section as ConfigSection] as Record<string, unknown>;
    return field ? sectionData[field] : sectionData;
  }

  async setValue(dottedKey: string, value: string): Promise<unknown> {
    const parts = dottedKey.split('.');
    const section = parts[0] ?? '';
    const field = parts.slice(1).join('.');
    if (!this.isValidSection(section) || !field) {
      throw new Error(
        `Invalid key "${dottedKey}". Use format <section>.<field> (e.g. soul.agentName).`,
      );
    }

    const previous = await this.getValue(dottedKey);

    if (section === 'soul') {
      const current = await this.readSoul();
      await this.writeSoul({ ...current, [field]: value });
    } else if (section === 'user') {
      const current = await this.readUser();
      await this.writeUser({ ...current, [field]: value });
    } else {
      const current = await this.readTools();
      await this.writeTools({ ...current, [field]: value });
    }

    return previous;
  }

  getConfigPath(section?: ConfigSection): string {
    if (!section) return this.mosaicHome;
    const fileMap: Record<ConfigSection, string> = {
      soul: join(this.mosaicHome, 'SOUL.md'),
      user: join(this.mosaicHome, 'USER.md'),
      tools: join(this.mosaicHome, 'TOOLS.md'),
    };
    return fileMap[section];
  }

  isInitialized(): boolean {
    return (
      existsSync(join(this.mosaicHome, 'SOUL.md')) ||
      existsSync(join(this.mosaicHome, 'USER.md')) ||
      existsSync(join(this.mosaicHome, 'TOOLS.md'))
    );
  }

  private isValidSection(s: string): s is ConfigSection {
    return s === 'soul' || s === 'user' || s === 'tools';
  }

  /**
   * Look for template in source dir first, then mosaic home.
   */
  private findTemplate(name: string): string | null {
    const candidates = [
      join(this.sourceDir, 'templates', name),
      join(this.mosaicHome, 'templates', name),
    ];
    for (const path of candidates) {
      if (existsSync(path)) return path;
    }
    return null;
  }
}
