import type { FastifyInstance } from "fastify";
import { MemoryCacheAdapter } from "../cache/memory-cache.js";
import { PgFormRepository } from "./repository.js";
import { FormService } from "./service.js";
import {
  CreateFormBodySchema,
  ListSubmissionsQuerySchema,
  PublishVersionBodySchema,
  SchemaQuerySchema,
  SubmissionBodySchema,
  UpdateFormBodySchema,
} from "./types.js";

// ─── Fastify JSON serialization schemas (fast-json-stringify) ─────────────────

const FormRecordSchema = {
  type: "object",
  properties: {
    id:               { type: "string" },
    code:             { type: "string" },
    displayName:      { type: "string" },
    description:      { type: ["string", "null"] },
    isActive:         { type: "boolean" },
    currentVersionId: { type: ["string", "null"] },
    createdAt:        { type: "string" },
    updatedAt:        { type: "string" },
  },
} as const;

const VersionSchema = {
  type: "object",
  properties: {
    id:             { type: "string" },
    formId:         { type: "string" },
    semver:         { type: "string" },
    schemaChecksum: { type: "string" },
    surveyJson:     { type: "object", additionalProperties: true },
    questionIndex:  { type: "object", additionalProperties: true },
    isPublished:    { type: "boolean" },
    publishedAt:    { type: ["string", "null"] },
    createdAt:      { type: "string" },
  },
} as const;

const FormWithVersionsSchema = {
  type: "object",
  properties: {
    ...FormRecordSchema.properties,
    versions:    { type: "array", items: VersionSchema },
    liveVersion: { ...VersionSchema, nullable: true },
  },
} as const;

const SubmissionSchema = {
  type: "object",
  properties: {
    id:             { type: "string" },
    submissionCode: { type: "string" },
    formId:         { type: "string" },
    formVersionId:  { type: "string" },
    moduleCode:     { type: "string" },
    submittedBy:    { type: ["string", "null"] },
    status:         { type: "string" },
    answers:        { type: "object", additionalProperties: true },
    submittedAt:    { type: "string" },
    createdAt:      { type: "string" },
  },
} as const;

// ─── Service singleton ────────────────────────────────────────────────────────

const service = new FormService(new PgFormRepository(), new MemoryCacheAdapter());

export async function registerFormRoutes(app: FastifyInstance): Promise<void> {

  // ── GET /api/forms ────────────────────────────────────────────────────────
  app.get("/api/forms", {
    schema: { response: { 200: { type: "array", items: FormRecordSchema } } },
  }, async (_request, reply) => {
    return reply.send(await service.listForms());
  });

  // ── GET /api/forms/schema?formCode=...&version=... ────────────────────────
  app.get("/api/forms/schema", async (request, reply) => {
    const parsed = SchemaQuerySchema.safeParse(request.query);
    if (!parsed.success) return reply.code(400).send({ message: parsed.error.message });
    try {
      return reply.send(await service.getSchema(parsed.data.formCode, parsed.data.version));
    } catch (err) {
      return reply.code(404).send({ message: (err as Error).message });
    }
  });

  // ── GET /api/forms/:code ──────────────────────────────────────────────────
  app.get<{ Params: { code: string } }>("/api/forms/:code", {
    schema: { response: { 200: FormWithVersionsSchema } },
  }, async (request, reply) => {
    const { code } = request.params;
    const form = await service.getFormByCode(code);
    if (!form) return reply.code(404).send({ message: `Form "${code}" not found` });
    return reply.send(form);
  });

  // ── POST /api/forms ───────────────────────────────────────────────────────
  app.post("/api/forms", async (request, reply) => {
    const parsed = CreateFormBodySchema.safeParse(request.body);
    if (!parsed.success) return reply.code(400).send({ message: parsed.error.message });
    try {
      const form = await service.createForm({
        code:           parsed.data.code,
        displayName:    parsed.data.displayName,
        description:    parsed.data.description,
        surveyJson:     parsed.data.surveyJson,
        schemaChecksum: parsed.data.schemaChecksum,
        semver:         parsed.data.semver,
        moduleCode:     parsed.data.moduleCode,
      });
      return reply.code(201).send(form);
    } catch (err) {
      const msg = (err as Error).message;
      if (msg.includes("23505") || msg.toLowerCase().includes("duplicate")) {
        return reply.code(409).send({ message: `Form code "${parsed.data.code}" already exists` });
      }
      throw err;
    }
  });

  // ── PATCH /api/forms/:code ────────────────────────────────────────────────
  app.patch<{ Params: { code: string } }>("/api/forms/:code", {
    schema: { response: { 200: FormWithVersionsSchema } },
  }, async (request, reply) => {
    const { code } = request.params;
    const parsed = UpdateFormBodySchema.safeParse(request.body);
    if (!parsed.success) return reply.code(400).send({ message: parsed.error.message });

    const form = await service.updateForm(code, {
      displayName:    parsed.data.displayName,
      description:    parsed.data.description,
      isActive:       parsed.data.isActive,
      surveyJson:     parsed.data.surveyJson,
      schemaChecksum: parsed.data.schemaChecksum,
      semver:         parsed.data.semver,
    });
    if (!form) return reply.code(404).send({ message: `Form "${code}" not found` });
    return reply.send(form);
  });

  // ── DELETE /api/forms/:code ───────────────────────────────────────────────
  app.delete<{ Params: { code: string } }>("/api/forms/:code", async (request, reply) => {
    const { code } = request.params;
    const deleted = await service.deleteForm(code);
    if (!deleted) return reply.code(404).send({ message: `Form "${code}" not found` });
    return reply.code(204).send();
  });

  // ── POST /api/forms/:code/versions ────────────────────────────────────────
  app.post<{ Params: { code: string } }>("/api/forms/:code/versions", async (request, reply) => {
    const { code } = request.params;
    const parsed = PublishVersionBodySchema.safeParse(request.body);
    if (!parsed.success) return reply.code(400).send({ message: parsed.error.message });
    try {
      const version = await service.publishVersion(code, {
        semver:         parsed.data.semver,
        surveyJson:     parsed.data.surveyJson,
        questionIndex:  parsed.data.questionIndex,
        schemaChecksum: parsed.data.schemaChecksum,
        publishedBy:    parsed.data.publishedBy,
      });
      return reply.code(201).send(version);
    } catch (err) {
      const msg = (err as Error).message;
      if (msg.includes("not found"))       return reply.code(404).send({ message: msg });
      if (msg.includes("already exists"))  return reply.code(409).send({ message: msg });
      throw err;
    }
  });

  // ── POST /api/forms/submissions ───────────────────────────────────────────
  app.post("/api/forms/submissions", async (request, reply) => {
    const parsed = SubmissionBodySchema.safeParse(request.body);
    if (!parsed.success) return reply.code(400).send({ message: parsed.error.message });
    try {
      const submission = await service.submit(
        parsed.data.formCode,
        parsed.data.version,
        parsed.data.moduleCode,
        parsed.data.answers,
        parsed.data.submittedBy
      );
      return reply.code(201).send(submission);
    } catch (err) {
      const msg = (err as Error).message;
      return reply.code(msg.includes("not found") ? 404 : 400).send({ message: msg });
    }
  });

  // ── GET /api/forms/:code/submissions ──────────────────────────────────────
  app.get<{ Params: { code: string } }>("/api/forms/:code/submissions", {
    schema: {
      response: {
        200: {
          type: "object",
          properties: {
            items: { type: "array", items: SubmissionSchema },
            total: { type: "number" },
            page:  { type: "number" },
            limit: { type: "number" },
          },
        },
      },
    },
  }, async (request, reply) => {
    const { code } = request.params;
    const form = await service.getFormByCode(code);
    if (!form) return reply.code(404).send({ message: `Form "${code}" not found` });

    const q = ListSubmissionsQuerySchema.omit({ formId: true }).safeParse(request.query);
    if (!q.success) return reply.code(400).send({ message: q.error.message });

    return reply.send(
      await service.listSubmissions(form.id, { page: q.data.page, limit: q.data.limit, status: q.data.status })
    );
  });
}
