import * as fs from "node:fs";
import { writeFile } from "node:fs/promises";
import path from "node:path";
import process from "node:process";

import { Command } from "@cliffy/command";
import { colors } from "@cliffy/ansi/colors";
import * as log from "../../core/log.ts";
import { yamlParseFile } from "../../utils/yaml.ts";
import { GlobalOptions } from "../../types.ts";
import { resolveWorkspace } from "../../core/context.ts";
import { requireLogin } from "../../core/auth.ts";
import * as wmill from "../../../gen/services.gen.ts";
import { DataTableSchema } from "../../../gen/types.gen.ts";
import { generateAgentsDocumentation } from "../sync/sync.ts";
import {
  getFolderSuffix,
  hasFolderSuffix,
  loadNonDottedPathsSetting,
} from "../../utils/resource_folders.ts";

interface GenerateAgentsOptions extends GlobalOptions {
  output?: string;
}

/**
 * Generates DATATABLES.md content from remote workspace datatable schemas.
 * This file focuses on schema information. For full instructions, see AGENTS.md.
 */
function generateDatatablesMarkdown(
  schemas: DataTableSchema[],
  localData?: {
    tables?: string[];
    datatable?: string;
    schema?: string;
  }
): string {
  const defaultDatatable = localData?.datatable;
  const defaultSchema = localData?.schema;
  const tables = localData?.tables ?? [];

  let content = `# Data Tables

This file contains database schema information for this app.
**For full instructions on using datatables, see \`AGENTS.md\`.**

## ⚠️ IMPORTANT

**You can ONLY use tables listed in \`data.tables\` in \`raw_app.yaml\`.**
To use a table from the schemas below, first add it to the whitelist.

## Current Configuration

${defaultDatatable
    ? `**Default Datatable:** \`${defaultDatatable}\`${defaultSchema ? ` | **Default Schema:** \`${defaultSchema}\`` : ''}`
    : `**No default datatable configured.** Set \`data.datatable\` in \`raw_app.yaml\`.`}

### Whitelisted Tables (You Can Use These)

${tables.length > 0
    ? tables.map((t) => `- \`${t}\``).join("\n")
    : `*No tables whitelisted yet.*`}

### To Add a Table

Edit \`raw_app.yaml\`:
\`\`\`yaml
data:
  datatable: ${defaultDatatable || 'main'}
  tables:
${tables.length > 0 ? tables.map((t) => `    - ${t}`).join("\n") : '    # Add tables here'}
    - ${defaultDatatable || 'main'}/${defaultSchema ? defaultSchema + ':' : ''}table_name  # ← Add like this
\`\`\`

---

## Available Schemas

`;

  if (schemas.length === 0) {
    content += `*No datatables configured in this workspace.*

Configure datatables in Workspace Settings > Windmill Data Tables.
`;
  } else {
    for (const dt of schemas) {
      const isDefault = dt.datatable_name === defaultDatatable;
      content += `### ${isDefault ? '★ ' : ''}Datatable: \`${dt.datatable_name}\`${isDefault ? ' (default)' : ''}

`;

      if (dt.error) {
        content += `> ⚠️ Error loading schema: ${dt.error}

`;
        continue;
      }

      if (!dt.schemas || Object.keys(dt.schemas).length === 0) {
        content += `*No schemas found.*

`;
        continue;
      }

      for (const [schemaName, schemaTables] of Object.entries(dt.schemas)) {
        const isDefaultSchema = schemaName === defaultSchema;
        content += `#### ${isDefaultSchema ? '★ ' : ''}Schema: \`${schemaName}\`${isDefaultSchema ? ' (default)' : ''}

`;

        if (!schemaTables || Object.keys(schemaTables).length === 0) {
          content += `*No tables.*

`;
          continue;
        }

        for (const [tableName, columns] of Object.entries(schemaTables)) {
          const fullTableRef = schemaName === 'public'
            ? `${dt.datatable_name}/${tableName}`
            : `${dt.datatable_name}/${schemaName}:${tableName}`;
          const isWhitelisted = tables.some(t =>
            t === fullTableRef ||
            t === `${dt.datatable_name}/${schemaName}.${tableName}` ||
            t === dt.datatable_name
          );

          content += `**\`${tableName}\`**${isWhitelisted ? ' ✓' : ''} → add as \`${fullTableRef}\`

| Column | Type |
|--------|------|
`;

          for (const [colName, colType] of Object.entries(columns)) {
            content += `| ${colName} | \`${colType}\` |
`;
          }

          content += `
`;
        }
      }
    }
  }

  content += `---
*Generated by \`wmill app generate-agents\`. See \`AGENTS.md\` for full instructions.*
`;

  return content;
}

/**
 * Regenerates AGENTS.md and DATATABLES.md for a raw app.
 * Can be called from CLI or programmatically (e.g., after SQL migration).
 */
export async function regenerateAgentDocs(
  workspaceId: string,
  targetDir: string,
  silent = false
): Promise<void> {
  // Check for raw_app.yaml
  const rawAppPath = path.join(targetDir, "raw_app.yaml");
  if (!fs.existsSync(rawAppPath)) {
    if (!silent) {
      log.error(colors.red(`Error: raw_app.yaml not found in ${targetDir}`));
    }
    return;
  }

  if (!silent) {
    log.info(colors.cyan("Refreshing agent documentation..."));
  }

  // Fetch schemas
  let schemas: DataTableSchema[] = [];
  try {
    schemas = await wmill.listDataTableSchemas({ workspace: workspaceId });
  } catch (error: unknown) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    if (!silent) {
      log.warn(colors.yellow(`Could not fetch datatable schemas: ${errorMessage}`));
    }
  }

  // Read local data configuration from raw_app.yaml
  let localData: { tables?: string[]; datatable?: string; schema?: string } | undefined;
  try {
    const rawApp = (await yamlParseFile(rawAppPath)) as Record<string, unknown>;
    if (rawApp.data && typeof rawApp.data === "object") {
      localData = rawApp.data as typeof localData;
    }
  } catch {
    // Ignore errors reading raw_app.yaml
  }

  // Generate and write AGENTS.md
  const agentsContent = generateAgentsDocumentation(localData);
  await writeFile(path.join(targetDir, "AGENTS.md"), agentsContent, "utf-8");

  // Generate and write CLAUDE.md referencing AGENTS.md
  await writeFile(path.join(targetDir, "CLAUDE.md"), `Instructions are in @AGENTS.md\n`, "utf-8");

  // Generate and write DATATABLES.md
  const datatablesContent = generateDatatablesMarkdown(schemas, localData);
  await writeFile(path.join(targetDir, "DATATABLES.md"), datatablesContent, "utf-8");

  if (!silent) {
    log.info(colors.green(`✓ Generated AGENTS.md, CLAUDE.md, and DATATABLES.md`));

    // Summary
    const datatableCount = schemas.length;
    const tableCount = schemas.reduce((acc, dt) => {
      if (!dt.schemas) return acc;
      return (
        acc +
        Object.values(dt.schemas).reduce((schemaAcc, tables) => {
          return schemaAcc + Object.keys(tables || {}).length;
        }, 0)
      );
    }, 0);

    log.info(colors.gray(`  Found ${datatableCount} datatable(s) with ${tableCount} table(s)`));

    if (localData?.datatable) {
      log.info(colors.gray(`  App configured for datatable: ${localData.datatable}`));
    }
  }
}

async function generateAgents(
  opts: GenerateAgentsOptions,
  appFolder?: string
) {
  // Resolve the app folder
  const cwd = process.cwd();
  let targetDir = cwd;

  if (appFolder) {
    targetDir = path.isAbsolute(appFolder)
      ? appFolder
      : path.join(cwd, appFolder);
  }

  // Load nonDottedPaths setting before using folder suffix functions
  await loadNonDottedPathsSetting();

  // Ensure we're in a raw_app folder or targeting one
  const dirName = path.basename(targetDir);
  if (!hasFolderSuffix(dirName, "raw_app")) {
    // Check if current directory is a raw_app folder
    if (!hasFolderSuffix(path.basename(cwd), "raw_app") && !appFolder) {
      log.error(
        colors.red(
          `Error: Must be run inside a ${getFolderSuffix("raw_app")} folder or specify one as argument.`
        )
      );
      log.info(colors.gray("Usage: wmill app generate-agents [app_folder]"));
      process.exit(1);
    }
  }

  // Check for raw_app.yaml
  const rawAppPath = path.join(targetDir, "raw_app.yaml");
  if (!fs.existsSync(rawAppPath)) {
    log.error(
      colors.red(`Error: raw_app.yaml not found in ${targetDir}`)
    );
    process.exit(1);
  }

  // Resolve workspace and authenticate
  const workspace = await resolveWorkspace(opts);
  await requireLogin(opts);

  await regenerateAgentDocs(workspace.workspaceId, targetDir);
}

const command = new Command()
  .description("regenerate AGENTS.md and DATATABLES.md from remote workspace")
  .arguments("[app_folder:string]")
  .action(generateAgents as any);

export default command;
