import type { Prisma } from "@prisma/client";
import { DepartmentKey } from "@prisma/client";
import { resolveHighestUserRole } from "@/lib/role-resolution";
import {
  clampBrandRating,
  mapMarketingInitiative,
  mapMarketingMilestone,
  mapMarketingTask,
} from "@/lib/marketing/persistence";
import { parseStringArray } from "@/lib/marketing/meetings";
import { projectLocations } from "@/lib/projects/mock";
import type {
  Employee,
  Initiative,
  Meeting,
  MeetingParticipant,
  MeetingParticipantCounts,
  MeetingResponseStatus,
  ProjectCalendarEvent,
  ProjectCalendarVisibility,
  ProjectCaptureEvidence,
  ProjectCaptureEvidenceKind,
} from "@/lib/projects/types";

export const PROJECT_LOCATION_IDS = projectLocations.map((location) => location.id);

function isLeaderRole(role: string | undefined): boolean {
  return role === "leader" || role === "lead";
}

export function canAccessProjects(role: string | undefined, department: string | null | undefined): boolean {
  return role === "owner" || department === DepartmentKey.proyectos;
}

export function canManageProjects(role: string | undefined, department: string | null | undefined): boolean {
  if (role === "owner") {
    return true;
  }

  return isLeaderRole(role) && department === DepartmentKey.proyectos;
}

export function mapProjectEmployee(user: {
  id: string;
  name: string | null;
  email: string;
  departmentRole: string | null;
  userRoles: Array<{ role: { key: string } }>;
}): Employee {
  const roleKey = resolveHighestUserRole(user.userRoles.map((entry) => entry.role.key));
  const roleTitle = user.departmentRole?.trim() || (roleKey === "leader" ? "Lider de Proyectos" : "Colaborador de Proyectos");

  return {
    id: user.id,
    name: user.name?.trim() || user.email,
    roleTitle,
    locationIds: PROJECT_LOCATION_IDS,
    isLead: roleKey === "leader",
  };
}

export const mapProjectTask = mapMarketingTask;
export const mapProjectMilestone = mapMarketingMilestone;

function roundToTwo(value: number): number {
  return Math.round(value * 100) / 100;
}

function revenueFromRecord(record: {
  type: string;
  revenueTarget: number;
  revenueActual: number;
  ticketsTarget: number;
  ticketsActual: number;
  targetTicketPrice: number;
  actualTicketPrice: number;
}) {
  if (record.type === "evento") {
    return {
      plannedRevenue: Math.max(0, record.ticketsTarget) * Math.max(0, record.targetTicketPrice),
      actualRevenue: Math.max(0, record.ticketsActual) * Math.max(0, record.actualTicketPrice),
    };
  }

  return {
    plannedRevenue: Math.max(0, record.revenueTarget),
    actualRevenue: Math.max(0, record.revenueActual),
  };
}

function variancePct(actual: number, planned: number): number {
  if (planned <= 0) {
    return 0;
  }

  return roundToTwo(((actual - planned) / planned) * 100);
}

export function mapProjectInitiative(initiative: {
  id: string;
  name: string;
  type: string;
  status: string;
  isGlobal: boolean;
  ownerId: string | null;
  dueDate: Date;
  completedAt: Date | null;
  importanceWeight: number;
  leadRating1to5: number;
  trackScore: boolean;
  trackTickets: boolean;
  trackRevenue: boolean;
  target: number;
  actual: number;
  ticketsTarget: number;
  ticketsActual: number;
  revenueTarget: number;
  revenueActual: number;
  plannedCost: number;
  actualCost: number;
  targetTicketPrice: number;
  actualTicketPrice: number;
  projectsSchemaVersion: number;
  opinionPublica: string;
  queFunciono: string;
  queNo: string;
  proximoIntento: string;
  updatedByName: string | null;
  updatedAt: Date;
  contributors: Array<{ userId: string }>;
  locations: Array<{ locationId: string }>;
  evidenceLinks: Array<{ url: string }>;
  tasks: Array<{ id: string }>;
  milestones?: Array<{
    id: string;
    initiativeId: string;
    title: string;
    description: string;
    dueDate: Date;
    status: string;
    sortOrder: number;
    checkpoints: Array<{
      id: string;
      note: string;
      createdAt: Date;
      createdByName: string | null;
    }>;
  }>;
}): Initiative {
  const base = mapMarketingInitiative(initiative);
  const { plannedRevenue, actualRevenue } = revenueFromRecord(initiative);
  const plannedCost = Math.max(0, initiative.plannedCost);
  const actualCost = Math.max(0, initiative.actualCost);
  const plannedProfit = plannedRevenue - plannedCost;
  const actualProfit = actualRevenue - actualCost;
  const actualMarginPct = actualRevenue > 0 ? roundToTwo((actualProfit / actualRevenue) * 100) : 0;
  const costVariancePct = variancePct(actualCost, plannedCost);
  const revenueVariancePct = variancePct(actualRevenue, plannedRevenue);

  return {
    ...base,
    leadRating1to5: clampBrandRating(base.leadRating1to5),
    plannedCost,
    actualCost,
    targetTicketPrice: Math.max(0, initiative.targetTicketPrice),
    actualTicketPrice: Math.max(0, initiative.actualTicketPrice),
    projectsSchemaVersion: initiative.projectsSchemaVersion,
    plannedRevenue: roundToTwo(plannedRevenue),
    actualRevenue: roundToTwo(actualRevenue),
    plannedProfit: roundToTwo(plannedProfit),
    actualProfit: roundToTwo(actualProfit),
    actualMarginPct,
    costVariancePct,
    revenueVariancePct,
  };
}

function clampMeetingResponseStatus(value: string): MeetingResponseStatus {
  if (value === "pending" || value === "accepted" || value === "declined") {
    return value;
  }

  return "pending";
}

function clampProjectCalendarVisibility(value: string): ProjectCalendarVisibility {
  if (value === "personal" || value === "team") {
    return value;
  }

  return "personal";
}

function clampProjectCaptureEvidenceKind(value: string): ProjectCaptureEvidenceKind {
  if (value === "photo" || value === "document" || value === "link") {
    return value;
  }

  return "document";
}

export function mapProjectMeetingParticipant(participant: {
  id: string;
  userId: string | null;
  displayName: string;
  responseStatus: string;
  respondedAt: Date | null;
}): MeetingParticipant {
  return {
    id: participant.id,
    userId: participant.userId,
    displayName: participant.displayName,
    responseStatus: clampMeetingResponseStatus(participant.responseStatus),
    respondedAt: participant.respondedAt ? participant.respondedAt.toISOString() : null,
  };
}

function getMeetingParticipantCounts(participants: MeetingParticipant[]): MeetingParticipantCounts {
  const counts = participants.reduce(
    (acc, participant) => {
      acc[participant.responseStatus] += 1;
      return acc;
    },
    {
      pending: 0,
      accepted: 0,
      declined: 0,
    }
  );

  return {
    ...counts,
    total: participants.length,
  };
}

export function mapProjectMeeting(record: {
  id: string;
  title: string;
  status: string;
  agenda: string;
  requestedById: string | null;
  requestedByName: string;
  participantNames: Prisma.JsonValue;
  suggestedTimes: Prisma.JsonValue;
  scheduledFor: Date | null;
  completedAt: Date | null;
  commitments: Array<{
    id: string;
    title: string;
    description: string | null;
    ownerName: string | null;
    dueDate: Date | null;
    status: string;
  }>;
  participants?: Array<{
    id: string;
    userId: string | null;
    displayName: string;
    responseStatus: string;
    respondedAt: Date | null;
  }>;
  calendarEvent?: { id: string } | null;
}): Meeting {
  const participants = (record.participants ?? []).map(mapProjectMeetingParticipant);
  const participantNames = participants.length > 0 ? participants.map((participant) => participant.displayName) : parseStringArray(record.participantNames);

  return {
    id: record.id,
    title: record.title,
    status:
      record.status === "requested" || record.status === "scheduled" || record.status === "completed" || record.status === "cancelled"
        ? record.status
        : "requested",
    agenda: record.agenda,
    requestedById: record.requestedById,
    requestedByName: record.requestedByName,
    participantNames,
    participants,
    participantCounts: getMeetingParticipantCounts(participants),
    suggestedTimes: parseStringArray(record.suggestedTimes),
    scheduledFor: record.scheduledFor ? record.scheduledFor.toISOString() : null,
    completedAt: record.completedAt ? record.completedAt.toISOString() : null,
    calendarEventId: record.calendarEvent?.id ?? null,
    commitments: record.commitments.map((commitment) => ({
      id: commitment.id,
      title: commitment.title,
      description: commitment.description,
      ownerName: commitment.ownerName,
      dueDate: commitment.dueDate ? commitment.dueDate.toISOString() : null,
      status: commitment.status,
    })),
  };
}

export function mapProjectCalendarEvent(event: {
  id: string;
  department: string;
  ownerUserId: string;
  title: string;
  notes: string | null;
  startAt: Date;
  endAt: Date;
  visibility: string;
  meetingId: string | null;
  createdAt: Date;
  updatedAt: Date;
}): ProjectCalendarEvent {
  return {
    id: event.id,
    department: "proyectos",
    ownerUserId: event.ownerUserId,
    title: event.title,
    notes: event.notes,
    startAt: event.startAt.toISOString(),
    endAt: event.endAt.toISOString(),
    visibility: clampProjectCalendarVisibility(event.visibility),
    meetingId: event.meetingId,
    createdAt: event.createdAt.toISOString(),
    updatedAt: event.updatedAt.toISOString(),
  };
}

export function mapProjectCaptureEvidence(record: {
  id: string;
  department: string;
  initiativeId: string | null;
  taskId: string | null;
  uploadedById: string;
  kind: string;
  title: string;
  note: string | null;
  url: string | null;
  storagePath: string | null;
  mimeType: string | null;
  sizeBytes: number | null;
  createdAt: Date;
  updatedAt: Date;
  uploadedBy: {
    name: string | null;
    email: string;
  };
}): ProjectCaptureEvidence {
  return {
    id: record.id,
    department: "proyectos",
    initiativeId: record.initiativeId,
    taskId: record.taskId,
    uploadedById: record.uploadedById,
    uploaderName: record.uploadedBy.name?.trim() || record.uploadedBy.email,
    kind: clampProjectCaptureEvidenceKind(record.kind),
    title: record.title,
    note: record.note,
    url: record.url,
    hasFile: Boolean(record.storagePath),
    mimeType: record.mimeType,
    sizeBytes: record.sizeBytes,
    createdAt: record.createdAt.toISOString(),
    updatedAt: record.updatedAt.toISOString(),
  };
}
