import {
  CreateCountrySchema,
  CreateMetroCitySchema,
  UpdateCountrySchema,
  UpdateMetroCitySchema,
} from "@capakraken/shared";
import type { Prisma } from "@capakraken/db";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { createAuditEntry } from "../lib/audit.js";
import {
  adminProcedure,
  createTRPCRouter,
  protectedProcedure,
  resourceOverviewProcedure,
} from "../trpc.js";
import {
  assertCountryCodeAvailable,
  assertMetroCityDeletable,
  buildCountryCreateData,
  buildCountryListWhere,
  buildCountryUpdateData,
  buildMetroCityCreateData,
  buildMetroCityUpdateData,
  findCountryByIdentifier,
} from "./country-support.js";

type CountryIdentifierReadModel = {
  id: string;
  code: string;
  name: string;
  isActive: boolean;
  dailyWorkingHours: number;
};

type CountryDetailReadModel = CountryIdentifierReadModel & {
  scheduleRules?: Prisma.JsonValue | null;
  metroCities: Array<{ id: string; name: string; countryId: string }>;
  _count: { resources: number };
};

export const countryRouter = createTRPCRouter({
  list: protectedProcedure
    .input(z.object({ isActive: z.boolean().optional() }).optional())
    .query(async ({ ctx, input }) => {
      return ctx.db.country.findMany({
        where: buildCountryListWhere(input ?? {}),
        include: { metroCities: { orderBy: { name: "asc" } } },
        orderBy: { name: "asc" },
      });
    }),

  resolveByIdentifier: protectedProcedure
    .input(z.object({ identifier: z.string().trim().min(1) }))
    .query(async ({ ctx, input }) => {
      return findCountryByIdentifier<CountryIdentifierReadModel>(ctx.db, input.identifier, {
        select: {
          id: true,
          code: true,
          name: true,
          isActive: true,
          dailyWorkingHours: true,
        },
      });
    }),

  getByIdentifier: resourceOverviewProcedure
    .input(z.object({ identifier: z.string().trim().min(1) }))
    .query(async ({ ctx, input }) => {
      return findCountryByIdentifier<CountryDetailReadModel>(ctx.db, input.identifier, {
        include: {
          metroCities: { orderBy: { name: "asc" } },
          _count: { select: { resources: true } },
        },
      });
    }),

  getById: resourceOverviewProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const country = await findUniqueOrThrow(
        ctx.db.country.findUnique({
          where: { id: input.id },
          include: {
            metroCities: { orderBy: { name: "asc" } },
            _count: { select: { resources: true } },
          },
        }),
        "Country",
      );
      return country;
    }),

  getCityById: protectedProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      const city = await findUniqueOrThrow(
        ctx.db.metroCity.findUnique({
          where: { id: input.id },
          select: { id: true, name: true, countryId: true },
        }),
        "Metro city",
      );
      return city;
    }),

  create: adminProcedure
    .input(CreateCountrySchema)
    .mutation(async ({ ctx, input }) => {
      await assertCountryCodeAvailable(ctx.db, input.code);
      const created = await ctx.db.country.create({
        data: buildCountryCreateData(input),
        include: { metroCities: true },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "Country",
        entityId: created.id,
        entityName: created.name,
        action: "CREATE",
        userId: ctx.dbUser?.id,
        after: created as unknown as Record<string, unknown>,
        source: "ui",
      });

      return created;
    }),

  update: adminProcedure
    .input(z.object({ id: z.string(), data: UpdateCountrySchema }))
    .mutation(async ({ ctx, input }) => {
      const existing = await findUniqueOrThrow(
        ctx.db.country.findUnique({ where: { id: input.id } }),
        "Country",
      );

      if (input.data.code && input.data.code !== existing.code) {
        await assertCountryCodeAvailable(ctx.db, input.data.code, existing.id);
      }

      const before = existing as unknown as Record<string, unknown>;

      const updated = await ctx.db.country.update({
        where: { id: input.id },
        data: buildCountryUpdateData(input.data),
        include: { metroCities: true },
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "Country",
        entityId: updated.id,
        entityName: updated.name,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        before,
        after: updated as unknown as Record<string, unknown>,
        source: "ui",
      });

      return updated;
    }),

  // ─── Metro City ─────────────────────────────────────────────

  createCity: adminProcedure
    .input(CreateMetroCitySchema)
    .mutation(async ({ ctx, input }) => {
      await findUniqueOrThrow(
        ctx.db.country.findUnique({ where: { id: input.countryId } }),
        "Country",
      );

      const created = await ctx.db.metroCity.create({
        data: buildMetroCityCreateData(input),
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "MetroCity",
        entityId: created.id,
        entityName: created.name,
        action: "CREATE",
        userId: ctx.dbUser?.id,
        after: created as unknown as Record<string, unknown>,
        source: "ui",
      });

      return created;
    }),

  updateCity: adminProcedure
    .input(z.object({ id: z.string(), data: UpdateMetroCitySchema }))
    .mutation(async ({ ctx, input }) => {
      const existing = await findUniqueOrThrow(
        ctx.db.metroCity.findUnique({ where: { id: input.id } }),
        "Metro city",
      );
      const before = existing as unknown as Record<string, unknown>;

      const updated = await ctx.db.metroCity.update({
        where: { id: input.id },
        data: buildMetroCityUpdateData(input.data),
      });

      void createAuditEntry({
        db: ctx.db,
        entityType: "MetroCity",
        entityId: updated.id,
        entityName: updated.name,
        action: "UPDATE",
        userId: ctx.dbUser?.id,
        before,
        after: updated as unknown as Record<string, unknown>,
        source: "ui",
      });

      return updated;
    }),

  deleteCity: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const city = await findUniqueOrThrow(
        ctx.db.metroCity.findUnique({
          where: { id: input.id },
          include: { _count: { select: { resources: true } } },
        }),
        "Metro city",
      );
      assertMetroCityDeletable(city);
      await ctx.db.metroCity.delete({ where: { id: input.id } });

      void createAuditEntry({
        db: ctx.db,
        entityType: "MetroCity",
        entityId: city.id,
        entityName: city.name,
        action: "DELETE",
        userId: ctx.dbUser?.id,
        before: city as unknown as Record<string, unknown>,
        source: "ui",
      });

      return { success: true, id: city.id, name: city.name };
    }),
});
