import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
import { join, resolve } from "node:path";
import { getDb, genId } from "../db.js";
import { getQuickRef } from "./toolRegistry.js";
import type { McpTool } from "../types.js";

// ── Helpers ──────────────────────────────────────────────────────────

function findConvexDir(projectDir: string): string | null {
  const candidates = [join(projectDir, "convex"), join(projectDir, "src", "convex")];
  for (const c of candidates) {
    if (existsSync(c)) return c;
  }
  return null;
}

function collectTsFiles(dir: string): string[] {
  const results: string[] = [];
  if (!existsSync(dir)) return results;
  const entries = readdirSync(dir, { withFileTypes: true });
  for (const entry of entries) {
    const full = join(dir, entry.name);
    if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "_generated") {
      results.push(...collectTsFiles(full));
    } else if (entry.isFile() && entry.name.endsWith(".ts")) {
      results.push(full);
    }
  }
  return results;
}

// ── Rules Generation Engine ─────────────────────────────────────────

function generateRulesContent(projectDir: string): string {
  const db = getDb();
  const convexDir = findConvexDir(projectDir);

  // Gather gotchas
  const gotchas = db.prepare(
    "SELECT key, content, category, severity FROM convex_gotchas ORDER BY severity DESC, category"
  ).all() as Array<{ key: string; content: string; category: string; severity: string }>;

  // Gather recent audit findings
  const recentAudits = db.prepare(
    "SELECT audit_type, issue_count, audited_at FROM audit_results WHERE project_dir = ? ORDER BY audited_at DESC LIMIT 5"
  ).all(projectDir) as Array<{ audit_type: string; issue_count: number; audited_at: string }>;

  // Gather deploy check history
  const deployChecks = db.prepare(
    "SELECT passed, checked_at FROM deploy_checks WHERE project_dir = ? ORDER BY checked_at DESC LIMIT 3"
  ).all(projectDir) as Array<{ passed: number; checked_at: string }>;

  // Count functions by type
  let functionStats = { total: 0, public: 0, internal: 0, httpActions: 0 };
  if (convexDir) {
    const files = collectTsFiles(convexDir);
    for (const f of files) {
      const content = readFileSync(f, "utf-8");
      const pubMatches = content.match(/export\s+const\s+\w+\s*=\s*(query|mutation|action)\s*\(/g);
      const intMatches = content.match(/export\s+const\s+\w+\s*=\s*(internalQuery|internalMutation|internalAction)\s*\(/g);
      const httpMatches = content.match(/httpAction\s*\(/g);
      functionStats.total += (pubMatches?.length || 0) + (intMatches?.length || 0) + (httpMatches?.length || 0);
      functionStats.public += pubMatches?.length || 0;
      functionStats.internal += intMatches?.length || 0;
      functionStats.httpActions += httpMatches?.length || 0;
    }
  }

  // Build rules markdown
  const lines: string[] = [];
  lines.push("# Convex Development Rules");
  lines.push(`# Auto-generated by convex-mcp-nodebench at ${new Date().toISOString()}`);
  lines.push(`# Project: ${projectDir}`);
  lines.push("");

  lines.push("## Project Stats");
  lines.push(`- Total functions: ${functionStats.total}`);
  lines.push(`- Public: ${functionStats.public}, Internal: ${functionStats.internal}, HTTP Actions: ${functionStats.httpActions}`);
  if (recentAudits.length > 0) {
    lines.push(`- Last audit: ${recentAudits[0].audit_type} (${recentAudits[0].issue_count} issues) at ${recentAudits[0].audited_at}`);
  }
  if (deployChecks.length > 0) {
    lines.push(`- Last deploy check: ${deployChecks[0].passed ? "PASSED" : "BLOCKED"} at ${deployChecks[0].checked_at}`);
  }
  lines.push("");

  // Group gotchas by category
  const categories = new Map<string, typeof gotchas>();
  for (const g of gotchas) {
    if (!categories.has(g.category)) categories.set(g.category, []);
    categories.get(g.category)!.push(g);
  }

  lines.push("## Convex Rules & Gotchas");
  lines.push("");

  for (const [category, items] of categories) {
    lines.push(`### ${category.charAt(0).toUpperCase() + category.slice(1)}`);
    for (const item of items) {
      const icon = item.severity === "critical" ? "CRITICAL" : item.severity === "warning" ? "WARNING" : "INFO";
      lines.push(`- **[${icon}]** \`${item.key}\`: ${item.content}`);
    }
    lines.push("");
  }

  lines.push("## Methodology");
  lines.push("1. Before any schema change: run `convex_audit_schema`");
  lines.push("2. Before any function change: run `convex_audit_functions`");
  lines.push("3. Before any deploy: run `convex_pre_deploy_gate`");
  lines.push("4. Before implementing: run `convex_search_gotchas` with your task description");
  lines.push("5. After fixing bugs: run `convex_record_gotcha` to prevent repeats");
  lines.push("");

  return lines.join("\n");
}

// ── Schema Snapshot Engine ──────────────────────────────────────────

function captureSchemaSnapshot(projectDir: string): { schemaJson: string; tableCount: number } | null {
  const convexDir = findConvexDir(projectDir);
  if (!convexDir) return null;

  const schemaPath = join(convexDir, "schema.ts");
  if (!existsSync(schemaPath)) return null;

  const schemaContent = readFileSync(schemaPath, "utf-8");

  // Extract table definitions (rough)
  const tablePattern = /(\w+)\s*[:=]\s*defineTable\s*\(/g;
  const tables: string[] = [];
  let m;
  while ((m = tablePattern.exec(schemaContent)) !== null) {
    tables.push(m[1]);
  }

  // Extract indexes per table
  const indexMap: Record<string, string[]> = {};
  const indexPattern = /\.index\s*\(\s*["']([^"']+)["']\s*,\s*\[([^\]]*)\]\s*\)/g;
  let currentTable = "";
  const lines = schemaContent.split("\n");
  for (let i = 0; i < lines.length; i++) {
    const tableDef = lines[i].match(/(\w+)\s*[:=]\s*defineTable\s*\(/);
    if (tableDef) currentTable = tableDef[1];
    const idxMatch = lines[i].match(/\.index\s*\(\s*["']([^"']+)["']/);
    if (idxMatch && currentTable) {
      if (!indexMap[currentTable]) indexMap[currentTable] = [];
      indexMap[currentTable].push(idxMatch[1]);
    }
  }

  const totalIndexes = Object.values(indexMap).reduce((sum, arr) => sum + arr.length, 0);

  const schemaJson = JSON.stringify({
    tables,
    indexes: indexMap,
    totalIndexes,
    rawLength: schemaContent.length,
    capturedAt: new Date().toISOString(),
  });

  return { schemaJson, tableCount: tables.length };
}

// ── Tool Definitions ────────────────────────────────────────────────

export const integrationBridgeTools: McpTool[] = [
  {
    name: "convex_generate_rules_md",
    description:
      "Generate or update a Convex rules markdown file from the current gotcha database, recent audit results, and project stats. Outputs to a specified path or returns the content. Use this to keep your AI rules file in sync with accumulated Convex knowledge.",
    inputSchema: {
      type: "object",
      properties: {
        projectDir: {
          type: "string",
          description: "Absolute path to the project root",
        },
        outputPath: {
          type: "string",
          description: "Optional: path to write the rules file (e.g., .windsurf/rules/convexRules.md). If omitted, returns content only.",
        },
      },
      required: ["projectDir"],
    },
    handler: async (args: { projectDir: string; outputPath?: string }) => {
      const projectDir = resolve(args.projectDir);
      const content = generateRulesContent(projectDir);

      if (args.outputPath) {
        const outputPath = resolve(args.outputPath);
        writeFileSync(outputPath, content, "utf-8");
        return {
          action: "written",
          outputPath,
          lines: content.split("\n").length,
          quickRef: getQuickRef("convex_get_methodology"),
        };
      }

      return {
        action: "generated",
        content,
        lines: content.split("\n").length,
        quickRef: getQuickRef("convex_get_methodology"),
      };
    },
  },
  {
    name: "convex_snapshot_schema",
    description:
      "Capture a snapshot of the current schema.ts for later diffing. Stores table names, schema size, and timestamp. Use before and after schema changes to track what changed.",
    inputSchema: {
      type: "object",
      properties: {
        projectDir: {
          type: "string",
          description: "Absolute path to the project root",
        },
      },
      required: ["projectDir"],
    },
    handler: async (args: { projectDir: string }) => {
      const projectDir = resolve(args.projectDir);
      const snapshot = captureSchemaSnapshot(projectDir);
      if (!snapshot) {
        return { error: "No schema.ts found" };
      }

      const db = getDb();
      const id = genId("snap");
      db.prepare(
        "INSERT INTO schema_snapshots (id, project_dir, schema_json) VALUES (?, ?, ?)"
      ).run(id, projectDir, snapshot.schemaJson);

      // Compare with previous snapshot
      const previous = db.prepare(
        "SELECT schema_json, snapshot_at FROM schema_snapshots WHERE project_dir = ? AND id != ? ORDER BY snapshot_at DESC LIMIT 1"
      ).get(projectDir, id) as any;

      let diff: any = null;
      if (previous) {
        try {
          const prev = JSON.parse(previous.schema_json);
          const curr = JSON.parse(snapshot.schemaJson);
          const addedTables = curr.tables.filter((t: string) => !prev.tables.includes(t));
          const removedTables = prev.tables.filter((t: string) => !curr.tables.includes(t));

          // Index diff
          const prevIdx: Record<string, string[]> = prev.indexes || {};
          const currIdx: Record<string, string[]> = curr.indexes || {};
          const addedIndexes: string[] = [];
          const removedIndexes: string[] = [];
          const allTables = new Set([...Object.keys(prevIdx), ...Object.keys(currIdx)]);
          for (const table of allTables) {
            const pSet = new Set(prevIdx[table] || []);
            const cSet = new Set(currIdx[table] || []);
            for (const idx of cSet) if (!pSet.has(idx)) addedIndexes.push(`${table}.${idx}`);
            for (const idx of pSet) if (!cSet.has(idx)) removedIndexes.push(`${table}.${idx}`);
          }

          diff = {
            previousSnapshot: previous.snapshot_at,
            addedTables,
            removedTables,
            addedIndexes,
            removedIndexes,
            indexCountChange: (curr.totalIndexes || 0) - (prev.totalIndexes || 0),
            sizeChange: curr.rawLength - prev.rawLength,
          };
        } catch { /* ignore */ }
      }

      const parsed = JSON.parse(snapshot.schemaJson);
      return {
        snapshotId: id,
        tableCount: snapshot.tableCount,
        tables: parsed.tables,
        totalIndexes: parsed.totalIndexes,
        indexes: parsed.indexes,
        diff,
        quickRef: getQuickRef("convex_audit_schema"),
      };
    },
  },
  {
    name: "convex_bootstrap_project",
    description:
      "Scan a Convex project directory and generate a comprehensive health report. Checks: schema presence, function count, validator coverage, index usage, env vars, auth setup, and known gotchas. Returns a prioritized improvement plan.",
    inputSchema: {
      type: "object",
      properties: {
        projectDir: {
          type: "string",
          description: "Absolute path to the project root",
        },
      },
      required: ["projectDir"],
    },
    handler: async (args: { projectDir: string }) => {
      const projectDir = resolve(args.projectDir);
      const convexDir = findConvexDir(projectDir);
      if (!convexDir) {
        return { error: "No convex/ directory found" };
      }

      const checks: Array<{ area: string; status: "good" | "warning" | "critical"; detail: string }> = [];

      // Check schema.ts
      const schemaPath = join(convexDir, "schema.ts");
      if (existsSync(schemaPath)) {
        const content = readFileSync(schemaPath, "utf-8");
        const tableCount = (content.match(/defineTable\s*\(/g) || []).length;
        const indexCount = (content.match(/\.index\s*\(/g) || []).length;
        checks.push({ area: "Schema", status: "good", detail: `${tableCount} tables, ${indexCount} indexes` });
        if (indexCount === 0 && tableCount > 0) {
          checks.push({ area: "Indexes", status: "warning", detail: "No indexes defined — queries will scan full tables" });
        }
        if (/v\.bigint\s*\(/.test(content)) {
          checks.push({ area: "Deprecated validators", status: "critical", detail: "Uses v.bigint() — replace with v.int64()" });
        }
      } else {
        checks.push({ area: "Schema", status: "warning", detail: "No schema.ts — using inferred schema" });
      }

      // Check auth
      if (existsSync(join(convexDir, "auth.ts"))) {
        if (existsSync(join(convexDir, "auth.config.ts"))) {
          checks.push({ area: "Auth", status: "good", detail: "auth.ts + auth.config.ts present" });
        } else {
          checks.push({ area: "Auth", status: "critical", detail: "auth.ts exists but auth.config.ts is missing" });
        }
      }

      // Check _generated
      if (existsSync(join(convexDir, "_generated"))) {
        checks.push({ area: "Initialization", status: "good", detail: "_generated/ present" });
      } else {
        checks.push({ area: "Initialization", status: "critical", detail: "Run 'npx convex dev' to initialize" });
      }

      // Count files
      const files = collectTsFiles(convexDir);
      checks.push({ area: "Codebase", status: "good", detail: `${files.length} TypeScript files in convex/` });

      // Generate improvement plan
      const plan: string[] = [];
      const criticals = checks.filter((c) => c.status === "critical");
      const warnings = checks.filter((c) => c.status === "warning");

      if (criticals.length > 0) {
        plan.push("CRITICAL: Fix these first:");
        for (const c of criticals) plan.push(`  - [${c.area}] ${c.detail}`);
      }
      if (warnings.length > 0) {
        plan.push("WARNINGS: Address these next:");
        for (const w of warnings) plan.push(`  - [${w.area}] ${w.detail}`);
      }
      plan.push("RECOMMENDED: Run convex_audit_schema → convex_audit_functions → convex_pre_deploy_gate");

      return {
        projectDir,
        convexDir,
        checks,
        summary: {
          good: checks.filter((c) => c.status === "good").length,
          warnings: warnings.length,
          criticals: criticals.length,
        },
        improvementPlan: plan,
        quickRef: getQuickRef("convex_get_methodology"),
      };
    },
  },
];
