import { Router } from "express";
import type { Response } from "express";
import crypto from "node:crypto";
import { z } from "zod";
import {
  getLatestProjectScan,
  ProjectScanError,
  scanProject,
  updateProjectPageSelection
} from "../scans/projectScanner.js";
import { LocalFolderValidationError } from "./localFolder.js";
import {
  copyProject,
  createBrowserFolderProject,
  createProject,
  deleteProject,
  listProjects,
  ProjectCopyError,
  ProjectNameConflictError,
  updateProjectName
} from "./projectRepository.js";
import {
  getOrCreatePageWorkspace,
  PageWorkspaceError,
  resetPageWorkspace,
  savePageWorkspace
} from "../workspace/pageWorkspace.js";
import {
  getLatestSemanticAnalysis,
  getSemanticAnalysisExport,
  runSemanticAnalysis,
  SemanticAnalysisError
} from "../analysis/semanticAnalysis.js";
import { getMarketEnrichmentContract, runMarketEnrichment } from "../market/marketEnrichment.js";
import { storageProvider } from "../storage/index.js";

export const projectsRouter = Router();

const createProjectSchema = z.object({
  name: z.string().trim().max(120, "Название слишком длинное.").optional().default(""),
  sourcePath: z.string().trim().min(1, "Путь к локальной папке обязателен.")
});

const browserFolderFileSchema = z.object({
  relativePath: z.string().trim().min(1),
  contentBase64: z.string().min(1),
  contentType: z.string().optional(),
  size: z.number().int().nonnegative(),
  lastModified: z.number().optional()
});

const importBrowserFolderSchema = z.object({
  name: z.string().trim().max(120, "Название слишком длинное.").optional().default(""),
  rootName: z.string().trim().min(1, "Не удалось определить имя папки."),
  files: z.array(browserFolderFileSchema).min(1, "В выбранной папке нет файлов для импорта.")
});

const projectIdSchema = z.string().uuid("Некорректный id проекта.");

const updateProjectNameSchema = z.object({
  name: z.string().trim().min(1, "Название проекта обязательно.").max(120, "Название слишком длинное.")
});

const updatePageSelectionSchema = z.object({
  pageIds: z.array(z.string().uuid("Некорректный id страницы.")).min(1, "Нужно выбрать хотя бы одну страницу."),
  selected: z.boolean()
});

const updatePageWorkspaceSchema = z.object({
  workingText: z.string().max(1_000_000, "Рабочий текст слишком большой для текущего MVP.")
});

const semanticExportFormatSchema = z.enum(["csv", "json", "markdown"]).default("json");
const scopeLayoutKeySchema = z.string().trim().min(1).max(240);
const scopeLayoutSchema = z.object({
  version: z.number().int().positive(),
  viewport: z
    .object({
      x: z.number().finite(),
      y: z.number().finite(),
      zoom: z.number().finite().positive()
    })
    .optional(),
  nodes: z.record(
    z.string(),
    z.object({
      position: z.object({
        x: z.number().finite(),
        y: z.number().finite()
      }),
      width: z.number().finite().positive().optional(),
      height: z.number().finite().positive().optional()
    })
  )
});

const MAX_BROWSER_FOLDER_FILES = 1500;
const MAX_BROWSER_FOLDER_BYTES = 100 * 1024 * 1024;

function sendError(response: Response, status: number, message: string) {
  response.status(status).json({
    error: {
      message
    }
  });
}

function getScopeLayoutObjectKey(projectId: string, layoutKey: string) {
  const digest = crypto.createHash("sha256").update(layoutKey).digest("hex").slice(0, 40);

  return `project-scope-layouts/${projectId}/${digest}.json`;
}

projectsRouter.get("/", async (_request, response) => {
  try {
    response.json({
      projects: await listProjects()
    });
  } catch (error) {
    console.error(error);
    sendError(response, 500, "Не удалось загрузить проекты.");
  }
});

projectsRouter.post("/", async (request, response) => {
  try {
    const input = createProjectSchema.parse(request.body);
    const project = await createProject(input);

    response.status(201).json({
      project
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Проверь данные проекта.");
      return;
    }

    if (error instanceof LocalFolderValidationError) {
      sendError(response, 400, error.message);
      return;
    }

    if (error instanceof ProjectNameConflictError) {
      sendError(response, 409, error.message);
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось создать проект.");
  }
});

projectsRouter.post("/import-folder", async (request, response) => {
  try {
    const input = importBrowserFolderSchema.parse(request.body);
    const totalBytes = input.files.reduce((sum, file) => sum + file.size, 0);

    if (input.files.length > MAX_BROWSER_FOLDER_FILES) {
      sendError(response, 400, `Слишком много файлов: максимум ${MAX_BROWSER_FOLDER_FILES}.`);
      return;
    }

    if (totalBytes > MAX_BROWSER_FOLDER_BYTES) {
      sendError(response, 400, "Папка слишком большая для первого импорта: максимум 100 МБ.");
      return;
    }

    const project = await createBrowserFolderProject(input);

    response.status(201).json({
      project
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Проверь данные импорта папки.");
      return;
    }

    if (error instanceof ProjectNameConflictError) {
      sendError(response, 409, error.message);
      return;
    }

    if (error instanceof ProjectCopyError) {
      sendError(response, 400, error.message);
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось импортировать папку.");
  }
});

projectsRouter.post("/:projectId/copy", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const project = await copyProject(projectId);

    if (!project) {
      sendError(response, 404, "Проект не найден.");
      return;
    }

    response.status(201).json({
      project
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id проекта.");
      return;
    }

    if (error instanceof ProjectNameConflictError) {
      sendError(response, 409, error.message);
      return;
    }

    if (error instanceof Error && error.message) {
      sendError(response, 400, error.message);
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось скопировать проект.");
  }
});

projectsRouter.get("/:projectId/scans/latest", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);

    response.json({
      scan: await getLatestProjectScan(projectId)
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id проекта.");
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось загрузить последний скан проекта.");
  }
});

projectsRouter.post("/:projectId/scans", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const scan = await scanProject(projectId);

    if (!scan) {
      sendError(response, 404, "Проект не найден.");
      return;
    }

    response.status(201).json({
      scan
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id проекта.");
      return;
    }

    if (error instanceof ProjectScanError) {
      sendError(response, 400, error.message);
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось просканировать проект.");
  }
});

projectsRouter.get("/:projectId/analysis/semantic/latest", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);

    response.json({
      analysis: await getLatestSemanticAnalysis(projectId)
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id проекта.");
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось загрузить semantic analysis проекта.");
  }
});

projectsRouter.post("/:projectId/analysis/semantic", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const analysis = await runSemanticAnalysis(projectId);

    if (!analysis) {
      sendError(response, 404, "Сначала нужен успешный скан проекта.");
      return;
    }

    response.status(201).json({
      analysis
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id проекта.");
      return;
    }

    if (error instanceof SemanticAnalysisError) {
      sendError(response, 400, error.message);
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось выполнить semantic analysis проекта.");
  }
});

projectsRouter.get("/:projectId/analysis/semantic/export", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const format = semanticExportFormatSchema.parse(request.query.format);
    const exportedAnalysis = await getSemanticAnalysisExport(projectId, format);

    if (!exportedAnalysis) {
      sendError(response, 404, "Сначала нужен semantic analysis проекта.");
      return;
    }

    response
      .status(200)
      .setHeader("Content-Type", exportedAnalysis.contentType)
      .setHeader("Content-Disposition", `attachment; filename="${exportedAnalysis.filename}"`)
      .send(exportedAnalysis.body);
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный формат экспорта.");
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось выгрузить semantic export проекта.");
  }
});

projectsRouter.get("/:projectId/market-enrichment/latest", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);

    response.json({
      marketEnrichment: await getMarketEnrichmentContract(projectId)
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id проекта.");
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось загрузить market enrichment проекта.");
  }
});

projectsRouter.post("/:projectId/market-enrichment", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const marketEnrichment = await runMarketEnrichment(projectId);

    if (!marketEnrichment.semanticRunId) {
      sendError(response, 404, "Сначала нужен semantic analysis проекта.");
      return;
    }

    response.status(202).json({
      marketEnrichment
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id проекта.");
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось запустить market enrichment проекта.");
  }
});

projectsRouter.get("/:projectId/scope-layouts/:layoutKey", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const layoutKey = scopeLayoutKeySchema.parse(request.params.layoutKey);

    try {
      const rawLayout = await storageProvider.getObjectText(getScopeLayoutObjectKey(projectId, layoutKey));
      const layout = scopeLayoutSchema.parse(JSON.parse(rawLayout));

      response.json({ layout });
    } catch {
      response.json({ layout: null });
    }
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный ключ layout.");
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось загрузить layout workflow.");
  }
});

projectsRouter.put("/:projectId/scope-layouts/:layoutKey", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const layoutKey = scopeLayoutKeySchema.parse(request.params.layoutKey);
    const layout = scopeLayoutSchema.parse(request.body.layout);

    await storageProvider.putObject({
      body: JSON.stringify(layout),
      contentType: "application/json; charset=utf-8",
      key: getScopeLayoutObjectKey(projectId, layoutKey)
    });

    response.json({ layout });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Проверь layout workflow.");
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось сохранить layout workflow.");
  }
});

projectsRouter.patch("/:projectId/pages/selection", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const input = updatePageSelectionSchema.parse(request.body);
    const result = await updateProjectPageSelection(projectId, input.pageIds, input.selected);

    response.json(result);
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Проверь выбор страниц.");
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось обновить выбор страниц.");
  }
});

projectsRouter.post("/:projectId/pages/:pageId/workspace", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const pageId = projectIdSchema.parse(request.params.pageId);
    const workspace = await getOrCreatePageWorkspace(projectId, pageId);

    if (!workspace) {
      sendError(response, 404, "Страница не найдена в последнем успешном скане проекта.");
      return;
    }

    response.status(201).json({
      workspace
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id страницы.");
      return;
    }

    if (error instanceof PageWorkspaceError) {
      sendError(response, 400, error.message);
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось открыть рабочую зону страницы.");
  }
});

projectsRouter.patch("/:projectId/pages/:pageId/workspace", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const pageId = projectIdSchema.parse(request.params.pageId);
    const input = updatePageWorkspaceSchema.parse(request.body);
    const workspace = await savePageWorkspace(projectId, pageId, input.workingText);

    if (!workspace) {
      sendError(response, 404, "Страница не найдена в последнем успешном скане проекта.");
      return;
    }

    response.json({
      workspace
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Проверь рабочий текст страницы.");
      return;
    }

    if (error instanceof PageWorkspaceError) {
      sendError(response, 400, error.message);
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось сохранить рабочий draft страницы.");
  }
});

projectsRouter.post("/:projectId/pages/:pageId/workspace/reset", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const pageId = projectIdSchema.parse(request.params.pageId);
    const workspace = await resetPageWorkspace(projectId, pageId);

    if (!workspace) {
      sendError(response, 404, "Страница не найдена в последнем успешном скане проекта.");
      return;
    }

    response.json({
      workspace
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id страницы.");
      return;
    }

    if (error instanceof PageWorkspaceError) {
      sendError(response, 400, error.message);
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось сбросить рабочий draft страницы.");
  }
});

projectsRouter.patch("/:projectId", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const input = updateProjectNameSchema.parse(request.body);
    const project = await updateProjectName(projectId, input.name);

    if (!project) {
      sendError(response, 404, "Проект не найден.");
      return;
    }

    response.json({
      project
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Проверь название проекта.");
      return;
    }

    if (error instanceof ProjectNameConflictError) {
      sendError(response, 409, error.message);
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось переименовать проект.");
  }
});

projectsRouter.delete("/:projectId", async (request, response) => {
  try {
    const projectId = projectIdSchema.parse(request.params.projectId);
    const deleted = await deleteProject(projectId);

    if (!deleted) {
      sendError(response, 404, "Проект не найден.");
      return;
    }

    response.status(204).send();
  } catch (error) {
    if (error instanceof z.ZodError) {
      sendError(response, 400, error.issues[0]?.message ?? "Некорректный id проекта.");
      return;
    }

    console.error(error);
    sendError(response, 500, "Не удалось удалить проект.");
  }
});
