import type { FastifyInstance } from "fastify";
import { z } from "zod";
import { requireSession } from "../../lib/auth.js";
import { writeAuditLog } from "../../lib/audit.js";
import {
  listClientSponsorsByClientIds,
  type ClientSponsorSummary,
} from "../../lib/client-sponsors.js";
import { createId } from "../../lib/id.js";
import { prisma } from "../../lib/prisma.js";
import { getSessionProfile } from "../../lib/session.js";

const clientInputSchema = z.object({
  firstName: z.string().min(1).max(100),
  lastName: z.string().min(1).max(100),
  email: z.string().email().optional().or(z.literal("")),
  phone: z.string().max(50).optional().or(z.literal("")),
  preferredLanguage: z.string().max(30).optional().or(z.literal("")),
});

const clientCreateSchema = clientInputSchema;

const clientIdParamSchema = z.object({
  clientId: z.string().uuid(),
});

const clientImportSchema = z.object({
  clients: z.array(clientInputSchema).min(1).max(500),
});

const clientSponsorUpdateSchema = z
  .object({
    clear: z.boolean().optional().default(false),
    entityType: z.enum(["person", "company"]).optional().default("company"),
    firstName: z.string().max(100).optional().or(z.literal("")),
    lastName: z.string().max(100).optional().or(z.literal("")),
    companyName: z.string().max(255).optional().or(z.literal("")),
    email: z.string().email().optional().or(z.literal("")),
    phone: z.string().max(50).optional().or(z.literal("")),
  })
  .superRefine((value, ctx) => {
    if (value.clear) {
      return;
    }

    if (value.entityType === "company" && !normalizeOptionalValue(value.companyName)) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: "companyName is required when entityType is company",
        path: ["companyName"],
      });
    }

    if (
      value.entityType === "person" &&
      !normalizeOptionalValue(value.firstName) &&
      !normalizeOptionalValue(value.lastName)
    ) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: "firstName or lastName is required when entityType is person",
        path: ["firstName"],
      });
    }
  });

const clientListQuerySchema = z.object({
  q: z.string().trim().max(100).optional(),
  limit: z.coerce.number().int().min(1).max(100).optional(),
});

type ClientInput = z.infer<typeof clientInputSchema>;

type ClientRecord = {
  id: string;
  client_number: string | null;
  first_name: string;
  last_name: string;
  email: string | null;
  phone: string | null;
  preferred_language: string | null;
  created_at: Date;
};

type SerializedClient = ReturnType<typeof serializeClient>;

function formatClientNumber(sequence: number) {
  return `CL-${String(sequence).padStart(6, "0")}`;
}

function normalizeOptionalValue(value: string | undefined) {
  const normalized = value?.trim();
  return normalized ? normalized : null;
}

function serializeClient(client: ClientRecord, sponsor: ClientSponsorSummary | null = null) {
  return {
    id: client.id,
    clientNumber: client.client_number,
    name: `${client.first_name} ${client.last_name}`.trim(),
    email: client.email,
    phone: client.phone,
    preferredLanguage: client.preferred_language,
    createdAt: client.created_at,
    sponsor,
  };
}

async function serializeClientsWithSponsors(
  lawFirmId: string,
  clients: ClientRecord[],
): Promise<SerializedClient[]> {
  const sponsorMap = await listClientSponsorsByClientIds({
    lawFirmId,
    clientIds: clients.map((client) => client.id),
  });

  return clients.map((client) => serializeClient(client, sponsorMap.get(client.id) ?? null));
}

async function currentClientCount(lawFirmId: string) {
  const count = await prisma.client.count({
    where: { law_firm_id: lawFirmId },
  });

  return count;
}

async function createClients(
  lawFirmId: string,
  officeId: string | null,
  userId: string,
  clients: ClientInput[],
) {
  const baseCount = await currentClientCount(lawFirmId);

  return prisma.$transaction(
    clients.map((client, index) =>
      prisma.client.create({
        data: {
          id: createId(),
          law_firm_id: lawFirmId,
          primary_office_id: officeId,
          client_number: formatClientNumber(baseCount + index + 1),
          first_name: client.firstName.trim(),
          last_name: client.lastName.trim(),
          email: normalizeOptionalValue(client.email),
          phone: normalizeOptionalValue(client.phone),
          preferred_language: normalizeOptionalValue(client.preferredLanguage),
          created_by_user_id: userId,
        },
      }),
    ),
  );
}

export async function registerClientRoutes(app: FastifyInstance) {
  app.get("/", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);
    const query = clientListQuerySchema.parse(request.query);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const terms = (query.q ?? "")
      .trim()
      .split(/\s+/)
      .filter(Boolean)
      .slice(0, 5);

    const clients = await prisma.client.findMany({
      where: {
        law_firm_id: profile.lawFirm.id,
        deleted_at: null,
        ...(terms.length > 0
          ? {
              AND: terms.map((term) => ({
                OR: [
                  {
                    first_name: {
                      contains: term,
                    },
                  },
                  {
                    last_name: {
                      contains: term,
                    },
                  },
                  {
                    email: {
                      contains: term,
                    },
                  },
                  {
                    phone: {
                      contains: term,
                    },
                  },
                  {
                    client_number: {
                      contains: term,
                    },
                  },
                ],
              })),
            }
          : {}),
      },
      orderBy: {
        created_at: "desc",
      },
      ...(query.limit ? { take: query.limit } : {}),
    });

    return serializeClientsWithSponsors(profile.lawFirm.id, clients);
  });

  app.post("/", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const payload = clientCreateSchema.parse(request.body);
    const [client] = await createClients(
      profile.lawFirm.id,
      profile.user.primaryOfficeId ?? null,
      profile.user.id,
      [payload],
    );

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "client",
      entityId: client.id,
      action: "client.create",
      afterJson: {
        clientNumber: client.client_number,
        firstName: client.first_name,
        lastName: client.last_name,
        email: client.email,
      },
      request,
    });

    return reply.code(201).send(serializeClient(client));
  });

  app.post("/import", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const payload = clientImportSchema.parse(request.body);
    const clients = await createClients(
      profile.lawFirm.id,
      profile.user.primaryOfficeId ?? null,
      profile.user.id,
      payload.clients,
    );

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "client_import",
      entityId: null,
      action: "client.import",
      afterJson: {
        importedCount: clients.length,
        clientIds: clients.map((client) => client.id),
      },
      request,
    });

    return reply.code(201).send({
      importedCount: clients.length,
      clients: clients.map((client) => serializeClient(client)),
    });
  });

  app.patch("/:clientId/sponsor", async (request, reply) => {
    const session = await requireSession(request, reply);
    const profile = await getSessionProfile(session);

    if (!profile) {
      throw reply.unauthorized("Session is no longer valid");
    }

    const { clientId } = clientIdParamSchema.parse(request.params);
    const payload = clientSponsorUpdateSchema.parse(request.body);

    const client = await prisma.client.findFirst({
      where: {
        id: clientId,
        law_firm_id: profile.lawFirm.id,
        deleted_at: null,
      },
    });

    if (!client) {
      throw reply.notFound("Client not found");
    }

    const existingSponsors = await prisma.relatedParty.findMany({
      where: {
        law_firm_id: profile.lawFirm.id,
        client_id: clientId,
        relation_type: "sponsor",
        deleted_at: null,
      },
      orderBy: [{ updated_at: "desc" }, { created_at: "desc" }],
    });

    const primarySponsor = existingSponsors[0] ?? null;
    const duplicateSponsorIds = existingSponsors.slice(1).map((item) => item.id);
    const now = new Date();

    if (payload.clear) {
      if (existingSponsors.length > 0) {
        await prisma.relatedParty.updateMany({
          where: {
            id: {
              in: existingSponsors.map((item) => item.id),
            },
          },
          data: {
            deleted_at: now,
          },
        });
      }

      await writeAuditLog({
        lawFirmId: profile.lawFirm.id,
        officeId: profile.user.primaryOfficeId ?? null,
        actorUserId: profile.user.id,
        entityType: "client_sponsor",
        entityId: clientId,
        action: "client.sponsor.clear",
        request,
      });

      return serializeClient(client, null);
    }

    const sponsorData = {
      entity_type: payload.entityType,
      first_name: normalizeOptionalValue(payload.firstName),
      last_name: normalizeOptionalValue(payload.lastName),
      company_name: normalizeOptionalValue(payload.companyName),
      email: normalizeOptionalValue(payload.email),
      phone: normalizeOptionalValue(payload.phone),
    };

    const sponsor =
      primarySponsor ?
        await prisma.relatedParty.update({
          where: {
            id: primarySponsor.id,
          },
          data: {
            ...sponsorData,
            deleted_at: null,
          },
        }) :
        await prisma.relatedParty.create({
          data: {
            id: createId(),
            law_firm_id: profile.lawFirm.id,
            client_id: clientId,
            case_id: null,
            relation_type: "sponsor",
            entity_type: sponsorData.entity_type,
            first_name: sponsorData.first_name,
            last_name: sponsorData.last_name,
            company_name: sponsorData.company_name,
            email: sponsorData.email,
            phone: sponsorData.phone,
          },
        });

    if (duplicateSponsorIds.length > 0) {
      await prisma.relatedParty.updateMany({
        where: {
          id: {
            in: duplicateSponsorIds,
          },
        },
        data: {
          deleted_at: now,
        },
      });
    }

    const serializedSponsor: ClientSponsorSummary = {
      id: sponsor.id,
      entityType: sponsor.entity_type === "company" ? "company" : "person",
      name:
        sponsor.entity_type === "company" ?
          sponsor.company_name?.trim() || sponsor.preferred_name?.trim() || "Unnamed sponsor" :
          [sponsor.first_name?.trim(), sponsor.last_name?.trim()].filter(Boolean).join(" ") ||
          sponsor.preferred_name?.trim() ||
          sponsor.company_name?.trim() ||
          "Unnamed sponsor",
      firstName: sponsor.first_name,
      lastName: sponsor.last_name,
      companyName: sponsor.company_name,
      email: sponsor.email,
      phone: sponsor.phone,
    };

    await writeAuditLog({
      lawFirmId: profile.lawFirm.id,
      officeId: profile.user.primaryOfficeId ?? null,
      actorUserId: profile.user.id,
      entityType: "client_sponsor",
      entityId: sponsor.id,
      action: primarySponsor ? "client.sponsor.update" : "client.sponsor.create",
      afterJson: {
        clientId,
        entityType: serializedSponsor.entityType,
        name: serializedSponsor.name,
        email: serializedSponsor.email,
        phone: serializedSponsor.phone,
      },
      request,
    });

    return serializeClient(client, serializedSponsor);
  });
}
