import type { MultipartFile } from "@fastify/multipart";
import type { FastifyInstance } from "fastify";
import { Prisma } from "@prisma/client";
import bcrypt from "bcryptjs";
import { z } from "zod";
import { clearSessionCookie, requireSession, setSessionCookie } from "../../lib/auth.js";
import { writeAuditLog } from "../../lib/audit.js";
import { createId } from "../../lib/id.js";
import {
  isImageKitConfigured,
  uploadBinaryFileToImageKit,
} from "../../lib/imagekit.js";
import { prisma } from "../../lib/prisma.js";
import { getSessionIdentity } from "../../lib/session.js";
import { slugify } from "../../lib/slug.js";
import {
  ensureLawFirmTeamUser,
  ensureWorkspaceMembership,
  splitName,
} from "../../lib/user-onboarding.js";

const signupSchema = z.object({
  name: z.string().min(2).max(150),
  email: z.string().email(),
  phone: z.string().min(7).max(50),
  password: z.string().min(8).max(128),
});

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8).max(128),
});

const createWorkspaceSchema = z.object({
  name: z.string().min(2).max(150),
});

const switchWorkspaceSchema = z.object({
  lawFirmId: z.string().uuid(),
});

const createWorkspaceInvitationSchema = z.object({
  email: z.string().email(),
});

const workspaceInvitationParamsSchema = z.object({
  invitationId: z.string().uuid(),
});

const respondWorkspaceInvitationSchema = z.object({
  decision: z.enum(["accepted", "rejected"]),
});

function normalizeEmail(value: string) {
  return String(value ?? "").trim().toLowerCase();
}

async function resolveUniqueSlug(name: string) {
  const baseSlug = slugify(name) || "law-firm";
  let slug = baseSlug;
  let suffix = 2;

  while (await prisma.lawFirm.findUnique({ where: { slug } })) {
    slug = `${baseSlug}-${suffix}`;
    suffix += 1;
  }

  return slug;
}

async function ensureEmailIsAvailable(email: string) {
  const existingUser = await prisma.user.findFirst({
    where: {
      email,
      deleted_at: null,
    },
    select: {
      id: true,
    },
  });

  if (existingUser) {
    throw new Error("Email is already in use");
  }
}

function serializeIdentity(
  identity: NonNullable<Awaited<ReturnType<typeof getSessionIdentity>>>,
) {
  return {
    lawFirm: identity.lawFirm,
    user: identity.user,
  };
}

function buildSessionPayload(input: {
  userId: string;
  lawFirmId: string | null;
  lawFirmSlug: string | null;
  email: string;
  roleCodes: string[];
  displayName: string;
}) {
  return {
    userId: input.userId,
    lawFirmId: input.lawFirmId,
    lawFirmSlug: input.lawFirmSlug,
    email: input.email,
    roleCodes: input.roleCodes,
    displayName: input.displayName,
  };
}

async function listAccessibleWorkspaces(input: {
  userId: string;
  currentLawFirmId: string | null;
}) {
  const preferredLawFirmId = input.currentLawFirmId ?? "";
  const rows = await prisma.$queryRaw<
    Array<{
      law_firm_id: string;
      law_firm_name: string;
      law_firm_slug: string;
      law_firm_status: string;
      membership_role: string;
      is_default: number;
      joined_at: Date;
    }>
  >`
    SELECT
      wm.law_firm_id,
      lf.name AS law_firm_name,
      lf.slug AS law_firm_slug,
      lf.status AS law_firm_status,
      wm.membership_role,
      wm.is_default,
      wm.joined_at
    FROM workspace_memberships wm
    INNER JOIN law_firms lf ON lf.id = wm.law_firm_id
    WHERE wm.user_id = ${input.userId}
      AND lf.deleted_at IS NULL
    ORDER BY
      CASE WHEN wm.law_firm_id = ${preferredLawFirmId} THEN 0 ELSE 1 END,
      CASE WHEN wm.is_default = 1 THEN 0 ELSE 1 END,
      wm.joined_at ASC
  `;

  return rows.map((row) => ({
    lawFirmId: row.law_firm_id,
    name: row.law_firm_name,
    slug: row.law_firm_slug,
    status: row.law_firm_status,
    membershipRole: row.membership_role,
    isDefault: Boolean(row.is_default),
    isCurrent: input.currentLawFirmId ? row.law_firm_id === input.currentLawFirmId : false,
    joinedAt: row.joined_at,
  }));
}

async function findPreferredWorkspace(input: {
  userId: string;
  preferredLawFirmId?: string | null;
}) {
  const preferredLawFirmId = input.preferredLawFirmId ?? "";
  const [row] = await prisma.$queryRaw<
    Array<{
      law_firm_id: string;
      law_firm_slug: string;
    }>
  >`
    SELECT
      wm.law_firm_id,
      lf.slug AS law_firm_slug
    FROM workspace_memberships wm
    INNER JOIN law_firms lf ON lf.id = wm.law_firm_id
    WHERE wm.user_id = ${input.userId}
      AND lf.deleted_at IS NULL
      AND lf.status = 'active'
    ORDER BY
      CASE WHEN wm.law_firm_id = ${preferredLawFirmId} THEN 0 ELSE 1 END,
      CASE WHEN wm.is_default = 1 THEN 0 ELSE 1 END,
      wm.joined_at ASC
    LIMIT 1
  `;

  if (!row) {
    return null;
  }

  return {
    lawFirmId: row.law_firm_id,
    lawFirmSlug: row.law_firm_slug,
  };
}

async function listPendingWorkspaceInvitations(input: {
  userId: string;
  userEmail: string;
}) {
  const email = normalizeEmail(input.userEmail);
  const rows = await prisma.$queryRaw<
    Array<{
      id: string;
      law_firm_id: string;
      law_firm_name: string;
      law_firm_slug: string;
      invitee_email: string;
      created_at: Date;
      invited_display_name: string | null;
      invited_first_name: string | null;
      invited_last_name: string | null;
      invited_email: string | null;
    }>
  >`
    SELECT
      wi.id,
      wi.law_firm_id,
      lf.name AS law_firm_name,
      lf.slug AS law_firm_slug,
      wi.invitee_email,
      wi.created_at,
      iu.display_name AS invited_display_name,
      iu.first_name AS invited_first_name,
      iu.last_name AS invited_last_name,
      iu.email AS invited_email
    FROM workspace_invitations wi
    INNER JOIN law_firms lf ON lf.id = wi.law_firm_id
    LEFT JOIN users iu ON iu.id = wi.invited_by_user_id
    WHERE wi.status_code = 'pending'
      AND lf.deleted_at IS NULL
      AND (
        wi.invitee_user_id = ${input.userId}
        OR LOWER(wi.invitee_email) = ${email}
      )
    ORDER BY wi.created_at ASC
  `;

  return rows.map((row) => ({
    id: row.id,
    lawFirmId: row.law_firm_id,
    lawFirmName: row.law_firm_name,
    lawFirmSlug: row.law_firm_slug,
    email: row.invitee_email,
    invitedAt: row.created_at,
    invitedBy:
      row.invited_display_name?.trim() ||
      `${String(row.invited_first_name ?? "").trim()} ${String(row.invited_last_name ?? "").trim()}`.trim() ||
      row.invited_email ||
      "Usuário",
  }));
}

async function setUserCurrentWorkspace(userId: string, lawFirmId: string | null) {
  await prisma.user.update({
    where: { id: userId },
    data: {
      law_firm_id: lawFirmId,
    },
  });
}

async function getCurrentWorkspaceMembershipRole(input: {
  lawFirmId: string;
  userId: string;
}) {
  const [membership] = await prisma.$queryRaw<
    Array<{
      membership_role: string;
    }>
  >`
    SELECT membership_role
    FROM workspace_memberships
    WHERE law_firm_id = ${input.lawFirmId}
      AND user_id = ${input.userId}
    LIMIT 1
  `;

  return membership?.membership_role ?? null;
}

export async function registerAuthRoutes(app: FastifyInstance) {
  app.post("/signup", async (request, reply) => {
    const payload = signupSchema.parse(request.body);
    const name = splitName(payload.name);
    const passwordHash = await bcrypt.hash(payload.password, 12);

    try {
      await ensureEmailIsAvailable(payload.email);

      const user = await prisma.$transaction(async (tx) => {
        const userId = createId();

        const createdUser = await tx.user.create({
          data: {
            id: userId,
            law_firm_id: null,
            primary_office_id: null,
            first_name: name.firstName,
            last_name: name.lastName,
            display_name: name.displayName,
            email: payload.email,
            password_hash: passwordHash,
            phone: payload.phone,
          },
        });

        await tx.userRole.create({
          data: {
            id: createId(),
            user_id: userId,
            role_code: "admin",
            office_id: null,
            scope_type: "law_firm",
          },
        });

        return createdUser;
      });

      const session = buildSessionPayload({
        userId: user.id,
        lawFirmId: null,
        lawFirmSlug: null,
        email: user.email,
        roleCodes: ["admin"],
        displayName: user.display_name ?? payload.name,
      });

      setSessionCookie(reply, session);

      return reply.code(201).send({
        message: "Account created successfully",
        session,
      });
    } catch (error) {
      if (
        error instanceof Prisma.PrismaClientKnownRequestError &&
        error.code === "P2002"
      ) {
        throw reply.conflict("A record with this unique value already exists");
      }

      if (error instanceof Error && error.message === "Email is already in use") {
        throw reply.conflict("This email is already in use");
      }

      throw error;
    }
  });

  app.post("/login", async (request, reply) => {
    const payload = loginSchema.parse(request.body);
    const user = await prisma.user.findFirst({
      where: {
        email: payload.email,
        is_active: true,
        deleted_at: null,
      },
    });

    if (!user?.password_hash) {
      throw reply.unauthorized("Invalid email or password");
    }

    const isValidPassword = await bcrypt.compare(
      payload.password,
      user.password_hash,
    );

    if (!isValidPassword) {
      throw reply.unauthorized("Invalid email or password");
    }

    const roles = await prisma.userRole.findMany({
      where: { user_id: user.id },
      select: { role_code: true },
    });

    const roleCodes = roles.map((role) => role.role_code);
    const preferredWorkspace = await findPreferredWorkspace({
      userId: user.id,
      preferredLawFirmId: user.law_firm_id,
    });

    await prisma.user.update({
      where: { id: user.id },
      data: {
        last_login_at: new Date(),
        law_firm_id: preferredWorkspace?.lawFirmId ?? null,
      },
    });

    const session = buildSessionPayload({
      userId: user.id,
      lawFirmId: preferredWorkspace?.lawFirmId ?? null,
      lawFirmSlug: preferredWorkspace?.lawFirmSlug ?? null,
      email: user.email,
      roleCodes,
      displayName: user.display_name ?? `${user.first_name} ${user.last_name}`.trim(),
    });

    setSessionCookie(reply, session);

    return {
      message: "Login successful",
      session,
    };
  });

  app.get("/me", async (request, reply) => {
    const session = await requireSession(request, reply);
    const identity = await getSessionIdentity(session);

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

    return serializeIdentity(identity);
  });

  app.post("/me/avatar", async (request, reply) => {
    const session = await requireSession(request, reply);
    const identity = await getSessionIdentity(session);

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

    if (!isImageKitConfigured()) {
      throw reply.serviceUnavailable("ImageKit não está configurado para upload de avatar.");
    }

    const multipartRequest = request as typeof request & {
      file: () => Promise<MultipartFile | undefined>;
    };
    const file = await multipartRequest.file();

    if (!file) {
      throw reply.badRequest("Selecione uma imagem para o avatar.");
    }

    if (!file.mimetype.startsWith("image/")) {
      throw reply.badRequest("Envie um arquivo de imagem válido.");
    }

    const buffer = await file.toBuffer();

    if (!buffer.length) {
      throw reply.badRequest("A imagem enviada está vazia.");
    }

    const stored = await uploadBinaryFileToImageKit({
      bytes: buffer,
      fileName: file.filename,
      mimeType: file.mimetype,
    });

    const avatarUrl =
      stored.uploadUrl ??
      (stored.objectKey.startsWith("http://") || stored.objectKey.startsWith("https://")
        ? stored.objectKey
        : null);

    if (!avatarUrl) {
      throw reply.internalServerError("ImageKit respondeu sem URL pública para o avatar.");
    }

    await prisma.user.update({
      where: { id: identity.user.id },
      data: {
        avatar_url: avatarUrl,
      },
    });

    if (identity.lawFirm) {
      await writeAuditLog({
        lawFirmId: identity.lawFirm.id,
        officeId: identity.user.primaryOfficeId ?? null,
        actorUserId: identity.user.id,
        entityType: "user",
        entityId: identity.user.id,
        action: "user.avatar.upload",
        afterJson: {
          avatarUrl,
        },
        request,
      });
    }

    return {
      avatarUrl,
    };
  });

  app.get("/workspaces", async (request, reply) => {
    const session = await requireSession(request, reply);
    const identity = await getSessionIdentity(session);

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

    return listAccessibleWorkspaces({
      userId: identity.user.id,
      currentLawFirmId: identity.lawFirm?.id ?? session.lawFirmId ?? null,
    });
  });

  app.get("/workspaces/invitations", async (request, reply) => {
    const session = await requireSession(request, reply);
    const identity = await getSessionIdentity(session);

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

    return listPendingWorkspaceInvitations({
      userId: identity.user.id,
      userEmail: identity.user.email,
    });
  });

  app.post("/workspaces", async (request, reply) => {
    const session = await requireSession(request, reply);
    const identity = await getSessionIdentity(session);
    const payload = createWorkspaceSchema.parse(request.body);

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

    if (!identity.user.roleCodes.includes("admin")) {
      throw reply.forbidden("Only admins can create workspaces");
    }

    const workspaceName = payload.name.trim();
    const slug = await resolveUniqueSlug(workspaceName);
    const lawFirmId = createId();

    const lawFirm = await prisma.$transaction(async (tx) => {
      const createdLawFirm = await tx.lawFirm.create({
        data: {
          id: lawFirmId,
          name: workspaceName,
          legal_name: workspaceName,
          slug,
          billing_email: identity.user.email,
        },
      });

      await ensureWorkspaceMembership(tx, {
        lawFirmId,
        userId: identity.user.id,
        membershipRole: "owner",
        createdByUserId: identity.user.id,
        isDefault: false,
      });

      await ensureLawFirmTeamUser(tx, {
        lawFirmId,
        userId: identity.user.id,
        displayName: identity.user.displayName,
        email: identity.user.email,
        membershipRole: "owner",
        invitedByUserId: identity.user.id,
        teamName: "Time interno",
      });

      await tx.lawFirmAiSetting.create({
        data: {
          id: createId(),
          law_firm_id: lawFirmId,
          default_provider: "openai",
          default_model: "gpt-5.4",
          is_active: true,
        },
      });

      return createdLawFirm;
    });

    await setUserCurrentWorkspace(identity.user.id, lawFirm.id);

    const nextSession = buildSessionPayload({
      userId: identity.user.id,
      lawFirmId: lawFirm.id,
      lawFirmSlug: lawFirm.slug,
      email: identity.user.email,
      roleCodes: identity.user.roleCodes,
      displayName: identity.user.displayName,
    });

    setSessionCookie(reply, nextSession);

    await writeAuditLog({
      lawFirmId: lawFirm.id,
      actorUserId: identity.user.id,
      officeId: null,
      entityType: "law_firm",
      entityId: lawFirm.id,
      action: "tenant.create_from_existing_user",
      afterJson: {
        workspaceName: lawFirm.name,
        slug: lawFirm.slug,
        createdFromUserId: identity.user.id,
      },
      request,
    });

    return reply.code(201).send({
      message: "Workspace created successfully",
      session: nextSession,
    });
  });

  app.post("/workspaces/switch", async (request, reply) => {
    const session = await requireSession(request, reply);
    const identity = await getSessionIdentity(session);
    const payload = switchWorkspaceSchema.parse(request.body);

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

    const workspaces = await listAccessibleWorkspaces({
      userId: identity.user.id,
      currentLawFirmId: identity.lawFirm?.id ?? session.lawFirmId ?? null,
    });
    const nextWorkspace = workspaces.find((workspace) => workspace.lawFirmId === payload.lawFirmId);

    if (!nextWorkspace) {
      throw reply.forbidden("You do not have access to this workspace");
    }

    if (nextWorkspace.status !== "active") {
      throw reply.forbidden("This workspace is inactive");
    }

    await setUserCurrentWorkspace(identity.user.id, nextWorkspace.lawFirmId);

    const nextSession = buildSessionPayload({
      userId: identity.user.id,
      lawFirmId: nextWorkspace.lawFirmId,
      lawFirmSlug: nextWorkspace.slug,
      email: identity.user.email,
      roleCodes: identity.user.roleCodes,
      displayName: identity.user.displayName,
    });

    setSessionCookie(reply, nextSession);

    return {
      message: "Workspace switched successfully",
      session: nextSession,
    };
  });

  app.post("/workspaces/invitations", async (request, reply) => {
    const session = await requireSession(request, reply);
    const identity = await getSessionIdentity(session);
    const payload = createWorkspaceInvitationSchema.parse(request.body);

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

    if (!identity.lawFirm) {
      throw reply.badRequest("Selecione um workspace antes de enviar convites.");
    }

    const membershipRole = await getCurrentWorkspaceMembershipRole({
      lawFirmId: identity.lawFirm.id,
      userId: identity.user.id,
    });

    if (!identity.user.roleCodes.includes("admin") && membershipRole !== "owner") {
      throw reply.forbidden("Only workspace owners or admins can invite users");
    }

    const email = normalizeEmail(payload.email);
    const inviteeUser = await prisma.user.findFirst({
      where: {
        email,
        is_active: true,
        deleted_at: null,
      },
      select: {
        id: true,
        email: true,
      },
    });

    if (!inviteeUser) {
      throw reply.badRequest("Use o e-mail de um usuário já cadastrado na plataforma.");
    }

    const existingMembership = await prisma.workspaceMembership.findFirst({
      where: {
        law_firm_id: identity.lawFirm.id,
        user_id: inviteeUser.id,
      },
      select: {
        id: true,
      },
    });

    if (existingMembership) {
      throw reply.conflict("This user already participates in this workspace");
    }

    const existingInvitation = await prisma.workspaceInvitation.findFirst({
      where: {
        law_firm_id: identity.lawFirm.id,
        invitee_email: email,
      },
    });

    if (existingInvitation?.status_code === "pending") {
      throw reply.conflict("There is already a pending invitation for this email");
    }

    if (existingInvitation) {
      await prisma.workspaceInvitation.update({
        where: { id: existingInvitation.id },
        data: {
          invitee_user_id: inviteeUser.id,
          invited_by_user_id: identity.user.id,
          status_code: "pending",
          responded_at: null,
        },
      });
    } else {
      await prisma.workspaceInvitation.create({
        data: {
          id: createId(),
          law_firm_id: identity.lawFirm.id,
          invitee_email: email,
          invitee_user_id: inviteeUser.id,
          invited_by_user_id: identity.user.id,
          status_code: "pending",
        },
      });
    }

    await writeAuditLog({
      lawFirmId: identity.lawFirm.id,
      actorUserId: identity.user.id,
      officeId: identity.user.primaryOfficeId ?? null,
      entityType: "law_firm",
      entityId: identity.lawFirm.id,
      action: "workspace.invite",
      afterJson: {
        inviteeEmail: email,
      },
      request,
    });

    return reply.code(201).send({
      message: "Workspace invitation sent successfully",
    });
  });

  app.post("/workspaces/invitations/:invitationId/respond", async (request, reply) => {
    const session = await requireSession(request, reply);
    const identity = await getSessionIdentity(session);
    const params = workspaceInvitationParamsSchema.parse(request.params);
    const payload = respondWorkspaceInvitationSchema.parse(request.body);

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

    const invitation = await prisma.workspaceInvitation.findUnique({
      where: { id: params.invitationId },
    });

    if (!invitation || invitation.status_code !== "pending") {
      throw reply.notFound("Invitation not found");
    }

    const inviteMatchesUser =
      invitation.invitee_user_id === identity.user.id ||
      normalizeEmail(invitation.invitee_email) === normalizeEmail(identity.user.email);

    if (!inviteMatchesUser) {
      throw reply.forbidden("This invitation is not assigned to your account");
    }

    const lawFirm = await prisma.lawFirm.findUnique({
      where: { id: invitation.law_firm_id },
      select: {
        id: true,
        slug: true,
        status: true,
        deleted_at: true,
      },
    });

    if (!lawFirm || lawFirm.deleted_at || lawFirm.status !== "active") {
      throw reply.forbidden("This workspace is inactive");
    }

    if (payload.decision === "accepted") {
      const existingMembership = await prisma.workspaceMembership.findFirst({
        where: {
          law_firm_id: invitation.law_firm_id,
          user_id: identity.user.id,
        },
        select: {
          id: true,
        },
      });

      if (!existingMembership) {
        const currentWorkspaceCount = await prisma.workspaceMembership.count({
          where: {
            user_id: identity.user.id,
          },
        });

        await ensureWorkspaceMembership(prisma, {
          lawFirmId: invitation.law_firm_id,
          userId: identity.user.id,
          membershipRole: "member",
          createdByUserId: invitation.invited_by_user_id ?? identity.user.id,
          isDefault: currentWorkspaceCount === 0,
        });
      }
    }

    await prisma.workspaceInvitation.update({
      where: { id: invitation.id },
      data: {
        invitee_user_id: identity.user.id,
        status_code: payload.decision,
        responded_at: new Date(),
      },
    });

    const nextWorkspace = await findPreferredWorkspace({
      userId: identity.user.id,
      preferredLawFirmId:
        payload.decision === "accepted"
          ? identity.lawFirm?.id ?? invitation.law_firm_id
          : identity.lawFirm?.id ?? session.lawFirmId ?? null,
    });

    await setUserCurrentWorkspace(identity.user.id, nextWorkspace?.lawFirmId ?? null);

    const nextSession = buildSessionPayload({
      userId: identity.user.id,
      lawFirmId: nextWorkspace?.lawFirmId ?? null,
      lawFirmSlug: nextWorkspace?.lawFirmSlug ?? null,
      email: identity.user.email,
      roleCodes: identity.user.roleCodes,
      displayName: identity.user.displayName,
    });

    setSessionCookie(reply, nextSession);

    if (payload.decision === "accepted") {
      await writeAuditLog({
        lawFirmId: invitation.law_firm_id,
        actorUserId: identity.user.id,
        officeId: identity.user.primaryOfficeId ?? null,
        entityType: "law_firm",
        entityId: invitation.law_firm_id,
        action: "workspace.invitation.accept",
        afterJson: {
          invitationId: invitation.id,
          inviteeEmail: identity.user.email,
        },
        request,
      });
    }

    return {
      message:
        payload.decision === "accepted"
          ? "Workspace invitation accepted successfully"
          : "Workspace invitation rejected successfully",
      session: nextSession,
    };
  });

  app.post("/logout", async (_request, reply) => {
    clearSessionCookie(reply);

    return {
      success: true,
    };
  });
}
