/**
 * Scaffold Service for SentryAgent.ai AgentIdP.
 * Generates in-memory ZIP archives containing language-specific starter projects
 * pre-wired with the requesting agent's credentials.
 */

import archiver from 'archiver';
import { PassThrough } from 'stream';
import { readFileSync } from 'fs';
import { join } from 'path';
import { ScaffoldLanguage, ScaffoldOptions } from '../types/scaffold.js';
import { ValidationError } from '../utils/errors.js';
import {
  scaffoldGeneratedTotal,
  scaffoldGenerationDurationMs,
} from '../metrics/registry.js';

/** Map of language → list of template file paths (relative to the language directory). */
const TEMPLATE_FILES: Record<ScaffoldLanguage, string[]> = {
  typescript: [
    'package.json.tmpl',
    'tsconfig.json.tmpl',
    'src/index.ts.tmpl',
    '.env.example.tmpl',
    '.gitignore.tmpl',
    'README.md.tmpl',
  ],
  python: [
    'requirements.txt.tmpl',
    'main.py.tmpl',
    '.env.example.tmpl',
    '.gitignore.tmpl',
    'README.md.tmpl',
  ],
  go: [
    'go.mod.tmpl',
    'main.go.tmpl',
    '.env.example.tmpl',
    '.gitignore.tmpl',
    'README.md.tmpl',
  ],
  java: [
    'pom.xml.tmpl',
    'src/main/java/Main.java.tmpl',
    '.env.example.tmpl',
    '.gitignore.tmpl',
    'README.md.tmpl',
  ],
  rust: [
    'Cargo.toml.tmpl',
    'src/main.rs.tmpl',
    '.env.example.tmpl',
    '.gitignore.tmpl',
    'README.md.tmpl',
  ],
};

/** Valid scaffold language values for input validation. */
export const SCAFFOLD_LANGUAGES: ScaffoldLanguage[] = [
  'typescript',
  'python',
  'go',
  'java',
  'rust',
];

/** Interface contract for the scaffold service. */
export interface IScaffoldService {
  /**
   * Generate an in-memory ZIP archive for the given agent and language.
   * Template variables injected: {{AGENT_ID}}, {{AGENT_NAME}}, {{CLIENT_ID}}, {{API_URL}}
   *
   * @param options - Scaffold generation options.
   * @returns Readable stream of the ZIP binary and the filename for Content-Disposition.
   */
  generateScaffold(options: ScaffoldOptions): Promise<{ stream: NodeJS.ReadableStream; filename: string }>;
}

/**
 * Implementation of IScaffoldService.
 * Generates scaffold ZIPs in memory using the `archiver` library — no disk writes.
 */
export class ScaffoldService implements IScaffoldService {
  private readonly templatesDir: string;

  constructor() {
    // __dirname is available because the project compiles to CommonJS
    this.templatesDir = join(__dirname, '..', 'templates', 'scaffold');
  }

  /**
   * Generates an in-memory ZIP scaffold for the given agent.
   *
   * @param options - Agent metadata and target language.
   * @returns Object with a readable stream of the ZIP and the ZIP filename.
   * @throws ValidationError if the language is not supported.
   */
  async generateScaffold(
    options: ScaffoldOptions,
  ): Promise<{ stream: NodeJS.ReadableStream; filename: string }> {
    const { agentId, agentName, clientId, language, apiUrl } = options;

    if (!SCAFFOLD_LANGUAGES.includes(language)) {
      throw new ValidationError(
        `Unsupported language '${language}'. Choose: ${SCAFFOLD_LANGUAGES.join(', ')}`,
        { code: 'INVALID_LANGUAGE' },
      );
    }

    const startMs = Date.now();
    const safeAgentName = agentName.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
    const projectDir = `sentryagent-scaffold-${safeAgentName}-${language}`;
    const filename = `${projectDir}.zip`;

    const archive = archiver('zip', { zlib: { level: 6 } });
    const passThrough = new PassThrough();
    archive.pipe(passThrough);

    const templateFiles = TEMPLATE_FILES[language];
    for (const templateFile of templateFiles) {
      const templatePath = join(this.templatesDir, language, templateFile);
      const rawContent = readFileSync(templatePath, 'utf-8');
      const content = this.injectVariables(rawContent, { agentId, agentName, clientId, apiUrl });
      // Strip .tmpl extension for the archive entry
      const archiveEntry = templateFile.endsWith('.tmpl')
        ? templateFile.slice(0, -5)
        : templateFile;
      archive.append(content, { name: `${projectDir}/${archiveEntry}` });
    }

    archive.finalize().catch((err: unknown) => {
      passThrough.destroy(err instanceof Error ? err : new Error(String(err)));
    });

    const durationMs = Date.now() - startMs;
    scaffoldGeneratedTotal.labels(language).inc();
    scaffoldGenerationDurationMs.labels(language).observe(durationMs);

    return { stream: passThrough, filename };
  }

  /** Replaces all template variable placeholders in a template string. */
  private injectVariables(
    template: string,
    vars: { agentId: string; agentName: string; clientId: string; apiUrl: string },
  ): string {
    return template
      .replace(/\{\{AGENT_ID\}\}/g, vars.agentId)
      .replace(/\{\{AGENT_NAME\}\}/g, vars.agentName)
      .replace(/\{\{CLIENT_ID\}\}/g, vars.clientId)
      .replace(/\{\{API_URL\}\}/g, vars.apiUrl);
  }
}
