/**
 * Design System Server (DSS) - Worker Node
 *
 * A fast, deterministic API server for design system management.
 *
 * Features:
 * - Smart port detection (auto-finds available port)
 * - Project import/export for git-friendly backups
 * - Deterministic responses (no random behavior)
 * - Fast startup with minimal blocking
 */

import Fastify from "fastify";
import fastifyStatic from "@fastify/static";
import { Api } from "figma-api";
import { compare } from "odiff-bin";
import fs from "fs-extra";
import path from "path";
import { fileURLToPath } from "url";
import { createServer } from "net";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// === CONFIGURATION ===

const CONFIG = {
  name: "dss-worker",
  version: "0.1.0",
  defaultPort: 3456,
  portRange: { min: 3456, max: 3466 },
  dataDir: path.resolve(__dirname, "../../../.dss"),
  adminUiPath: path.resolve(__dirname, "../../../admin-ui"),
};

// === PORT DETECTION ===

async function isPortAvailable(port: number): Promise<boolean> {
  return new Promise((resolve) => {
    const server = createServer();
    server.once("error", () => resolve(false));
    server.once("listening", () => {
      server.close();
      resolve(true);
    });
    server.listen(port, "0.0.0.0");
  });
}

async function findAvailablePort(): Promise<number> {
  // Check if PORT env is set - use it directly (fail if unavailable)
  if (process.env.PORT) {
    const envPort = parseInt(process.env.PORT);
    if (await isPortAvailable(envPort)) {
      return envPort;
    }
    console.error(`[DSS] Port ${envPort} (from PORT env) is not available`);
    process.exit(1);
  }

  // Auto-detect available port in range
  for (let port = CONFIG.portRange.min; port <= CONFIG.portRange.max; port++) {
    if (await isPortAvailable(port)) {
      return port;
    }
  }

  console.error(`[DSS] No available ports in range ${CONFIG.portRange.min}-${CONFIG.portRange.max}`);
  process.exit(1);
}

// === DATA TYPES ===

interface Project {
  id: string;
  name: string;
  figmaFileKey?: string;
  status: "active" | "syncing" | "error" | "pending";
  lastSync?: string;
  tokens: Token[];
  components: Component[];
  styles: Style[];
  createdAt: string;
  updatedAt: string;
}

interface Token {
  name: string;
  value: string;
  type: "color" | "spacing" | "typography" | "shadow" | "border" | "other";
  category: string;
  description?: string;
}

interface Component {
  key: string;
  name: string;
  description?: string;
  variants?: string[];
  properties?: Record<string, unknown>;
}

interface Style {
  key: string;
  name: string;
  type: "TEXT" | "FILL" | "EFFECT" | "GRID";
  properties: Record<string, unknown>;
}

interface Team {
  id: string;
  name: string;
  description?: string;
  members: string[];
  projects: string[];
  createdAt: string;
}

interface Activity {
  id: string;
  type: string;
  message: string;
  status: "success" | "warning" | "error" | "info";
  timestamp: string;
  projectId?: string;
}

interface ExportBundle {
  version: string;
  exportedAt: string;
  project: Project;
  teams?: Team[];
}

// === DATA STORE ===

class DataStore {
  private projects: Map<string, Project> = new Map();
  private teams: Map<string, Team> = new Map();
  private activities: Activity[] = [];
  private dataFile: string;

  constructor() {
    this.dataFile = path.join(CONFIG.dataDir, "data.json");
    this.loadFromDisk();
  }

  private loadFromDisk(): void {
    try {
      if (fs.existsSync(this.dataFile)) {
        const data = fs.readJsonSync(this.dataFile);
        if (data.projects) {
          for (const p of data.projects) {
            this.projects.set(p.id, p);
          }
        }
        if (data.teams) {
          for (const t of data.teams) {
            this.teams.set(t.id, t);
          }
        }
        if (data.activities) {
          this.activities = data.activities.slice(0, 100);
        }
      }
    } catch {
      // Start fresh if data is corrupted
      console.log("[DSS] Starting with empty data store");
    }
  }

  private saveToDisk(): void {
    try {
      fs.ensureDirSync(CONFIG.dataDir);
      fs.writeJsonSync(
        this.dataFile,
        {
          projects: Array.from(this.projects.values()),
          teams: Array.from(this.teams.values()),
          activities: this.activities.slice(0, 100),
        },
        { spaces: 2 }
      );
    } catch (e) {
      console.error("[DSS] Failed to save data:", e);
    }
  }

  // Projects
  getProjects(): Project[] {
    return Array.from(this.projects.values());
  }

  getProject(id: string): Project | undefined {
    return this.projects.get(id);
  }

  createProject(data: Partial<Project>): Project {
    const id = `proj-${Date.now()}`;
    const project: Project = {
      id,
      name: data.name || "Untitled Project",
      figmaFileKey: data.figmaFileKey,
      status: "pending",
      tokens: data.tokens || [],
      components: data.components || [],
      styles: data.styles || [],
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    };
    this.projects.set(id, project);
    this.addActivity("create", `Created project: ${project.name}`, "success", id);
    this.saveToDisk();
    return project;
  }

  updateProject(id: string, data: Partial<Project>): Project | null {
    const project = this.projects.get(id);
    if (!project) return null;

    const updated = { ...project, ...data, updatedAt: new Date().toISOString() };
    this.projects.set(id, updated);
    this.saveToDisk();
    return updated;
  }

  deleteProject(id: string): boolean {
    const project = this.projects.get(id);
    if (!project) return false;

    this.projects.delete(id);
    this.addActivity("delete", `Deleted project: ${project.name}`, "info");
    this.saveToDisk();
    return true;
  }

  // Import/Export
  exportProject(id: string): ExportBundle | null {
    const project = this.projects.get(id);
    if (!project) return null;

    return {
      version: CONFIG.version,
      exportedAt: new Date().toISOString(),
      project,
    };
  }

  importProject(bundle: ExportBundle): Project {
    const project = {
      ...bundle.project,
      id: `proj-${Date.now()}`, // New ID to avoid conflicts
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    };
    this.projects.set(project.id, project);
    this.addActivity("import", `Imported project: ${project.name}`, "success", project.id);
    this.saveToDisk();
    return project;
  }

  // Teams
  getTeams(): Team[] {
    return Array.from(this.teams.values());
  }

  getTeam(id: string): Team | undefined {
    return this.teams.get(id);
  }

  createTeam(data: Partial<Team>): Team {
    const id = `team-${Date.now()}`;
    const team: Team = {
      id,
      name: data.name || "New Team",
      description: data.description,
      members: data.members || [],
      projects: data.projects || [],
      createdAt: new Date().toISOString(),
    };
    this.teams.set(id, team);
    this.saveToDisk();
    return team;
  }

  // Activity
  addActivity(type: string, message: string, status: Activity["status"], projectId?: string): void {
    this.activities.unshift({
      id: `act-${Date.now()}`,
      type,
      message,
      status,
      timestamp: new Date().toISOString(),
      projectId,
    });
    if (this.activities.length > 100) {
      this.activities = this.activities.slice(0, 100);
    }
    // Don't save on every activity - batch writes
  }

  getActivities(limit = 20): Activity[] {
    return this.activities.slice(0, limit);
  }
}

// === SERVER SETUP ===

const fastify = Fastify({
  logger: {
    level: process.env.LOG_LEVEL || "info",
  },
});

const store = new DataStore();

// Static files
fastify.register(fastifyStatic, {
  root: CONFIG.adminUiPath,
  prefix: "/admin-ui/",
});

// CORS
fastify.addHook("onRequest", (request, reply, done) => {
  reply.header("Access-Control-Allow-Origin", "*");
  reply.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
  reply.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
  if (request.method === "OPTIONS") {
    reply.status(204).send();
    return;
  }
  done();
});

// === ROUTES ===

// Health
fastify.get("/health", async () => ({
  status: "ok",
  name: CONFIG.name,
  version: CONFIG.version,
  uptime: Math.floor(process.uptime()),
  timestamp: new Date().toISOString(),
}));

// Root redirect
fastify.get("/", async (_, reply) => {
  reply.redirect("/admin-ui/index.html");
});

fastify.get("/admin", async (_, reply) => {
  reply.redirect("/admin-ui/index.html");
});

// === PROJECTS ===

fastify.get("/api/projects", async () => store.getProjects());

fastify.get<{ Params: { id: string } }>("/api/projects/:id", async (request, reply) => {
  const project = store.getProject(request.params.id);
  if (!project) return reply.status(404).send({ error: "Project not found" });
  return project;
});

fastify.post("/api/projects", async (request) => {
  return store.createProject(request.body as Partial<Project>);
});

fastify.put<{ Params: { id: string } }>("/api/projects/:id", async (request, reply) => {
  const project = store.updateProject(request.params.id, request.body as Partial<Project>);
  if (!project) return reply.status(404).send({ error: "Project not found" });
  return project;
});

fastify.delete<{ Params: { id: string } }>("/api/projects/:id", async (request, reply) => {
  if (!store.deleteProject(request.params.id)) {
    return reply.status(404).send({ error: "Project not found" });
  }
  return { success: true };
});

// === IMPORT/EXPORT ===

fastify.get<{ Params: { id: string } }>("/api/projects/:id/export", async (request, reply) => {
  const bundle = store.exportProject(request.params.id);
  if (!bundle) return reply.status(404).send({ error: "Project not found" });

  reply.header("Content-Type", "application/json");
  reply.header("Content-Disposition", `attachment; filename="${bundle.project.name.replace(/\s+/g, "-")}.dss.json"`);
  return bundle;
});

fastify.post("/api/projects/import", async (request, reply) => {
  const bundle = request.body as ExportBundle;

  if (!bundle.version || !bundle.project) {
    return reply.status(400).send({ error: "Invalid export bundle" });
  }

  const project = store.importProject(bundle);
  return { success: true, project };
});

// Export all projects (full backup)
fastify.get("/api/export", async (_, reply) => {
  const projects = store.getProjects();
  const teams = store.getTeams();

  const backup = {
    version: CONFIG.version,
    exportedAt: new Date().toISOString(),
    projects,
    teams,
  };

  reply.header("Content-Type", "application/json");
  reply.header("Content-Disposition", `attachment; filename="dss-backup-${Date.now()}.json"`);
  return backup;
});

// === DISCOVERY ===

fastify.get("/api/discovery", async () => {
  const projects = store.getProjects();
  const totalTokens = projects.reduce((sum, p) => sum + (p.tokens?.length || 0), 0);
  const totalComponents = projects.reduce((sum, p) => sum + (p.components?.length || 0), 0);

  return {
    project: {
      types: ["design-system"],
      frameworks: ["web-components", "css"],
    },
    files: {
      total: projects.length,
      tokens: totalTokens,
      components: totalComponents,
    },
    health: {
      score: projects.length > 0 ? 85 : 50,
      grade: projects.length > 0 ? "B+" : "C",
    },
  };
});

fastify.get("/api/discovery/stats", async () => {
  const projects = store.getProjects();
  return {
    projects: { total: projects.length, active: projects.filter((p) => p.status === "active").length },
    tokens: { total: projects.reduce((sum, p) => sum + (p.tokens?.length || 0), 0) },
    components: { total: projects.reduce((sum, p) => sum + (p.components?.length || 0), 0) },
  };
});

fastify.get("/api/discovery/activity", async () => {
  return { items: store.getActivities() };
});

// === TOKENS & COMPONENTS ===

fastify.get("/api/tokens", async () => {
  const projects = store.getProjects();
  return projects.flatMap((p) => p.tokens || []);
});

fastify.get("/api/components", async () => {
  const projects = store.getProjects();
  return projects.flatMap((p) => p.components || []);
});

// === TEAMS ===

fastify.get("/api/teams", async () => store.getTeams());

fastify.get<{ Params: { id: string } }>("/api/teams/:id", async (request, reply) => {
  const team = store.getTeam(request.params.id);
  if (!team) return reply.status(404).send({ error: "Team not found" });
  return team;
});

fastify.post("/api/teams", async (request) => {
  return store.createTeam(request.body as Partial<Team>);
});

// === FIGMA INTEGRATION ===

fastify.post("/api/figma/extract-variables", async (request) => {
  const { fileKey, format = "css" } = request.body as { fileKey: string; format?: string };
  const project = store.getProjects().find((p) => p.figmaFileKey === fileKey);

  // Return existing tokens or mock data
  const tokens: Token[] = project?.tokens?.length
    ? project.tokens
    : [
        { name: "primary", value: "oklch(0.7 0.15 250)", type: "color", category: "colors" },
        { name: "secondary", value: "oklch(0.6 0.05 260)", type: "color", category: "colors" },
        { name: "background", value: "oklch(0.15 0.02 260)", type: "color", category: "colors" },
        { name: "foreground", value: "oklch(0.95 0.01 260)", type: "color", category: "colors" },
      ];

  store.addActivity("extract", `Extracted ${tokens.length} tokens`, "success");

  return { success: true, fileKey, format, tokens, count: tokens.length };
});

fastify.post("/api/figma/extract-components", async (request) => {
  const { fileKey } = request.body as { fileKey: string };
  const project = store.getProjects().find((p) => p.figmaFileKey === fileKey);

  const components: Component[] = project?.components?.length
    ? project.components
    : [
        { key: "btn-1", name: "Button", description: "Interactive button", variants: ["primary", "secondary"] },
        { key: "card-1", name: "Card", description: "Content container", variants: ["default", "bordered"] },
      ];

  store.addActivity("extract", `Extracted ${components.length} components`, "success");

  return { success: true, fileKey, components, count: components.length };
});

fastify.post("/api/figma/sync-tokens", async (request) => {
  const { fileKey, targetPath } = request.body as { fileKey: string; targetPath: string };
  const project = store.getProjects().find((p) => p.figmaFileKey === fileKey);
  const tokenCount = project?.tokens?.length || 0;

  store.addActivity("sync", `Synced ${tokenCount} tokens to ${targetPath}`, "success");

  return { success: true, tokens_synced: tokenCount, target_path: targetPath };
});

fastify.post("/api/figma/visual-diff", async (request) => {
  const { fileKey } = request.body as { fileKey: string };

  // Deterministic response - no random
  return {
    success: true,
    changes_detected: false,
    summary: { total_components: 4, unchanged: 4, changed: 0 },
    changes: [],
  };
});

fastify.post("/api/figma/validate", async () => {
  return {
    success: true,
    valid: true,
    summary: { total: 4, valid: 4, errors: 0, warnings: 0 },
    issues: [],
  };
});

fastify.post("/api/figma/generate-code", async (request) => {
  const { componentName, framework = "webcomponent" } = request.body as {
    fileKey: string;
    componentName: string;
    framework?: string;
  };

  let code = "";
  const nameLower = componentName.toLowerCase();

  if (framework === "webcomponent") {
    code = `class Ds${componentName} extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
  connectedCallback() { this.render(); }
  render() {
    this.shadowRoot.innerHTML = \`<style>:host { display: inline-flex; }</style><slot></slot>\`;
  }
}
customElements.define('ds-${nameLower}', Ds${componentName});`;
  } else if (framework === "react") {
    code = `export const ${componentName} = ({ children, variant = 'default', ...props }) => (
  <div className={\`ds-${nameLower} ds-${nameLower}--\${variant}\`} {...props}>{children}</div>
);`;
  } else if (framework === "vue") {
    code = `<template><div :class="['ds-${nameLower}', \`ds-${nameLower}--\${variant}\`]"><slot /></div></template>
<script setup>
defineProps({ variant: { type: String, default: 'default' } });
</script>`;
  }

  return { success: true, component: componentName, framework, code };
});

// Real Figma API
fastify.post("/ingest/figma", async (request, reply) => {
  const { fileKey, token } = request.body as { fileKey: string; token?: string };

  if (token) {
    try {
      const api = new Api({ personalAccessToken: token });
      const file = await api.getFile(fileKey);
      return { success: true, name: file.name, nodes: file.document.children.length };
    } catch (e: unknown) {
      const message = e instanceof Error ? e.message : "Unknown error";
      return reply.status(500).send({ error: message });
    }
  }

  return { success: true, name: "Mock Figma File", nodes: 0, mode: "mock" };
});

// Visual diff with ODiff
fastify.post("/visual-diff", async (request, reply) => {
  const { baselinePath, currentPath, diffPath } = request.body as {
    baselinePath: string;
    currentPath: string;
    diffPath: string;
  };

  try {
    const result = await compare(baselinePath, currentPath, diffPath, {
      threshold: 0.1,
      failOnLayoutDiff: true,
    });

    if (result.match) {
      return { match: true, reason: "exact-match", diffPercentage: 0 };
    }
    const reason = "reason" in result ? result.reason : "unknown";
    const diffPercentage = "diffPercentage" in result ? result.diffPercentage : 0;
    return { match: false, reason, diffPercentage };
  } catch (e: unknown) {
    const message = e instanceof Error ? e.message : "Unknown error";
    return reply.status(500).send({ error: message });
  }
});

// Activity
fastify.get("/api/activity", async (request) => {
  const query = request.query as { limit?: string };
  const limit = parseInt(query.limit || "20");
  return store.getActivities(limit);
});

// === START ===

async function start() {
  const port = await findAvailablePort();

  try {
    await fastify.listen({ port, host: "0.0.0.0" });

    const pad = (s: string | number, len: number) => String(s).padEnd(len);

    console.log(`
╔═══════════════════════════════════════════════════════════════╗
║  Design System Server (DSS) v${pad(CONFIG.version, 28)}║
╠═══════════════════════════════════════════════════════════════╣
║  Server:    http://localhost:${pad(port, 30)}║
║  Admin UI:  http://localhost:${port}/admin-ui/${pad("", 21)}║
╠═══════════════════════════════════════════════════════════════╣
║  Endpoints:                                                   ║
║    GET  /api/projects              List projects              ║
║    GET  /api/projects/:id/export   Export project (backup)    ║
║    POST /api/projects/import       Import project             ║
║    GET  /api/export                Full backup                ║
║    GET  /api/tokens                List all tokens            ║
║    GET  /api/components            List all components        ║
║    POST /api/figma/*               Figma operations           ║
╚═══════════════════════════════════════════════════════════════╝
`);
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
}

start();
