import type { Organization } from '@prisma/client';
import prisma from '../config/db.js';
import { encodeId } from '../utils/idCodec.js';
import { fileUrl } from '../utils/storage.js';
import { isOrganizationSuspended } from '../utils/organizationStatus.js';
import type {
  DepartmentBody,
  SubscriptionRequestBody,
  UpdateTenantOrganizationBody,
} from '../validators/tenantAdmin.validator.js';

// Organization profile, subscription, setup checklist, departments and teams
// for /api/v1/admin/*. Every query takes the caller's organizationId (from
// tenantAccess), so one tenant can never read or change another's rows.

const orEmpty = (value: string | null | undefined) => (value === '' ? null : value);

/** `logoUrl` is stored as an S3 key (or an older pasted URL) and returned as a URL to load. */
export async function organizationProfile(org: Organization) {
  return {
    id: encodeId(org.id),
    name: org.name,
    legalName: org.legalName ?? undefined,
    gstin: org.gstin ?? undefined,
    pan: org.pan ?? undefined,
    address: org.address ?? undefined,
    contactEmail: org.contactEmail ?? '',
    logoUrl: await fileUrl(org.logoUrl),
    timezone: org.timezone,
    currency: org.currency,
    financialYearStartMonth: org.financialYearStartMonth,
  };
}

/** Sets (or clears) the logo's S3 key; returns the organization and the key it replaced. */
export async function setLogo(organizationId: number, key: string | null) {
  const { logoUrl: previous } = await prisma.organization.findUniqueOrThrow({ where: { id: organizationId }, select: { logoUrl: true } });
  const org = await prisma.organization.update({ where: { id: organizationId }, data: { logoUrl: key } });
  return { org, previous };
}

export function getOrganization(organizationId: number) {
  return prisma.organization.findUniqueOrThrow({ where: { id: organizationId } });
}

export function updateOrganization(organizationId: number, body: UpdateTenantOrganizationBody) {
  const data: Record<string, unknown> = { ...body };
  for (const key of ['legalName', 'gstin', 'pan', 'address', 'contactEmail'] as const) {
    if (key in data) data[key] = orEmpty(body[key]);
  }
  return prisma.organization.update({ where: { id: organizationId }, data });
}

// Seats count active users (invited users will count too, once invites exist);
// deactivated users free their seat.
export function seatsUsed(organizationId: number) {
  return prisma.user.count({ where: { organizationId, isActive: true } });
}

export async function subscriptionView(org: Organization) {
  const trial = org.trialEndsAt !== null && org.trialEndsAt > new Date();
  return {
    planName: org.planName,
    status: isOrganizationSuspended(org) ? 'suspended' : trial ? 'trial' : 'active',
    moduleKeys: org.moduleKeys,
    limits: { maxUsers: org.maxUsers, maxProjects: org.maxProjects, storageGb: org.storageGb },
    usage: {
      maxUsers: await seatsUsed(org.id),
      maxProjects: await prisma.project.count({ where: { organizationId: org.id } }),
      storageGb: org.storageUsedGb,
    },
  };
}

export function createSubscriptionRequest(organizationId: number, requestedById: number, body: SubscriptionRequestBody) {
  return prisma.subscriptionRequest.create({ data: { organizationId, requestedById, ...body } });
}

export async function setupStatus(org: Organization) {
  const [teams, users, seats, customRoles, memberships] = await Promise.all([
    prisma.team.count({ where: { department: { organizationId: org.id } } }),
    prisma.user.count({ where: { organizationId: org.id } }),
    seatsUsed(org.id),
    prisma.role.count({ where: { organizationId: org.id } }),
    prisma.projectMember.count({ where: { project: { organizationId: org.id } } }),
  ]);
  return {
    dismissed: org.setupDismissed,
    seatsAvailable: Math.max(0, org.maxUsers - seats),
    steps: [
      { key: 'account', done: true },
      { key: 'profile', done: Boolean(org.legalName && org.address && org.gstin) },
      { key: 'departments', done: teams > 0 },
      { key: 'roles', done: org.rolesReviewed || customRoles > 0 },
      { key: 'invite', done: users > 1 },
      { key: 'projects', done: memberships > 0 },
    ],
  };
}

export function updateSetup(organizationId: number, body: { dismissed?: boolean; rolesReviewed?: boolean }) {
  return prisma.organization.update({
    where: { id: organizationId },
    data: {
      ...(body.dismissed !== undefined ? { setupDismissed: body.dismissed } : {}),
      ...(body.rolesReviewed !== undefined ? { rolesReviewed: body.rolesReviewed } : {}),
    },
  });
}

// ── Departments & teams ──

const activeMember = { isActive: true };

export async function departmentsView(organizationId: number) {
  const departments = await prisma.department.findMany({
    where: { organizationId },
    orderBy: { name: 'asc' },
    include: {
      head: { select: { name: true } },
      _count: { select: { members: { where: activeMember } } },
      teams: {
        orderBy: { name: 'asc' },
        include: {
          lead: { select: { name: true } },
          _count: { select: { members: { where: activeMember } } },
        },
      },
    },
  });
  return departments.map((d) => ({
    id: encodeId(d.id),
    name: d.name,
    headId: d.headId ? encodeId(d.headId) : undefined,
    headName: d.head?.name,
    archived: d.archived,
    memberCount: d._count.members,
    teams: d.teams.map((t) => ({
      id: encodeId(t.id),
      departmentId: encodeId(d.id),
      name: t.name,
      leadId: t.leadId ? encodeId(t.leadId) : undefined,
      leadName: t.lead?.name,
      memberCount: t._count.members,
    })),
  }));
}

export async function departmentView(organizationId: number, id: number) {
  return (await departmentsView(organizationId)).find((d) => d.id === encodeId(id));
}

export function findDepartment(organizationId: number, id: number) {
  return prisma.department.findFirst({ where: { id, organizationId } });
}

export function findDepartmentByName(organizationId: number, name: string) {
  return prisma.department.findFirst({ where: { organizationId, name: { equals: name, mode: 'insensitive' } } });
}

export function createDepartment(organizationId: number, data: { name: string; headId: number | null }) {
  return prisma.department.create({ data: { organizationId, ...data } });
}

export function updateDepartment(id: number, data: Omit<DepartmentBody, 'headId'> & { headId?: number | null }) {
  return prisma.department.update({ where: { id }, data });
}

export function countDepartmentMembers(id: number) {
  return prisma.user.count({ where: { departmentId: id } });
}

export async function deleteDepartment(id: number) {
  // Its teams go with it (ON DELETE CASCADE).
  await prisma.department.delete({ where: { id } });
}

export function findTeam(organizationId: number, id: number) {
  return prisma.team.findFirst({ where: { id, department: { organizationId } } });
}

export async function teamView(id: number) {
  const team = await prisma.team.findUniqueOrThrow({
    where: { id },
    include: { lead: { select: { name: true } }, _count: { select: { members: { where: activeMember } } } },
  });
  return {
    id: encodeId(team.id),
    departmentId: encodeId(team.departmentId),
    name: team.name,
    leadId: team.leadId ? encodeId(team.leadId) : undefined,
    leadName: team.lead?.name,
    memberCount: team._count.members,
  };
}

export function createTeam(data: { departmentId: number; name: string; leadId: number | null }) {
  return prisma.team.create({ data });
}

export function updateTeam(id: number, data: { name?: string; leadId?: number | null }) {
  return prisma.team.update({ where: { id }, data });
}

export async function deleteTeam(id: number) {
  // People stay in the department; users.team_id is cleared (ON DELETE SET NULL).
  await prisma.team.delete({ where: { id } });
}
