import { createAiClient, isAiConfigured, loggedAiCall, parseAiError } from "../ai-client.js";
import { controllerProcedure, createTRPCRouter } from "../trpc.js";
import { TRPCError } from "@trpc/server";
import { z } from "zod";

// ─── Types ───────────────────────────────────────────────────────────────────

export interface Anomaly {
  type: "budget" | "staffing" | "utilization" | "timeline";
  severity: "warning" | "critical";
  entityId: string;
  entityName: string;
  message: string;
}

interface InsightDemandRecord {
  headcount: number;
  startDate: Date;
  endDate: Date;
  _count: {
    assignments: number;
  };
}

interface InsightProjectAssignmentRecord {
  resourceId: string;
  startDate: Date;
  endDate: Date;
  hoursPerDay: number;
  dailyCostCents: number;
  status: string;
}

interface InsightProjectRecord {
  id: string;
  name: string;
  budgetCents: number;
  startDate: Date;
  endDate: Date;
  demandRequirements: InsightDemandRecord[];
  assignments: InsightProjectAssignmentRecord[];
}

interface InsightResourceRecord {
  id: string;
  displayName: string;
  availability: unknown;
}

interface InsightAssignmentLoadRecord {
  resourceId: string;
  hoursPerDay: number;
}

interface InsightSnapshot {
  anomalies: Anomaly[];
  summary: {
    total: number;
    criticalCount: number;
    budget: number;
    staffing: number;
    timeline: number;
    utilization: number;
  };
}

interface InsightsDbAccess {
  project: {
    findMany(args: Record<string, unknown>): Promise<InsightProjectRecord[]>;
  };
  resource: {
    findMany(args: Record<string, unknown>): Promise<InsightResourceRecord[]>;
  };
  assignment: {
    findMany(args: Record<string, unknown>): Promise<InsightAssignmentLoadRecord[]>;
  };
}

// ─── Helpers ─────────────────────────────────────────────────────────────────

/**
 * Count business days between two dates (Mon–Fri).
 */
function countBusinessDays(start: Date, end: Date): number {
  let count = 0;
  const d = new Date(start);
  while (d <= end) {
    const dow = d.getDay();
    if (dow !== 0 && dow !== 6) count++;
    d.setDate(d.getDate() + 1);
  }
  return count;
}

async function loadInsightProjects(db: InsightsDbAccess["project"]) {
  return db.findMany({
    where: { status: { in: ["ACTIVE", "DRAFT"] } },
    include: {
      demandRequirements: {
        select: {
          headcount: true,
          startDate: true,
          endDate: true,
          _count: { select: { assignments: true } },
        },
      },
      assignments: {
        select: {
          resourceId: true,
          startDate: true,
          endDate: true,
          hoursPerDay: true,
          dailyCostCents: true,
          status: true,
        },
      },
    },
  }) as Promise<InsightProjectRecord[]>;
}

async function loadInsightResources(db: InsightsDbAccess["resource"]) {
  return db.findMany({
    where: { isActive: true },
    select: {
      id: true,
      displayName: true,
      availability: true,
    },
  }) as Promise<InsightResourceRecord[]>;
}

async function loadInsightAssignmentLoads(db: InsightsDbAccess["assignment"], now: Date) {
  const periodStart = new Date(now.getFullYear(), now.getMonth(), 1);
  const periodEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);

  return db.findMany({
    where: {
      status: { in: ["ACTIVE", "CONFIRMED"] },
      startDate: { lte: periodEnd },
      endDate: { gte: periodStart },
    },
    select: {
      resourceId: true,
      hoursPerDay: true,
    },
  }) as Promise<InsightAssignmentLoadRecord[]>;
}

function summarizeAnomalies(anomalies: Anomaly[]): InsightSnapshot["summary"] {
  return anomalies.reduce<InsightSnapshot["summary"]>((summary, anomaly) => {
    summary.total += 1;
    summary[anomaly.type] += 1;
    if (anomaly.severity === "critical") {
      summary.criticalCount += 1;
    }
    return summary;
  }, {
    total: 0,
    criticalCount: 0,
    budget: 0,
    staffing: 0,
    timeline: 0,
    utilization: 0,
  });
}

async function buildInsightSnapshot(db: InsightsDbAccess, now = new Date()): Promise<InsightSnapshot> {
  const [projects, resources, activeAssignments] = await Promise.all([
    loadInsightProjects(db.project),
    loadInsightResources(db.resource),
    loadInsightAssignmentLoads(db.assignment, now),
  ]);

  const twoWeeksFromNow = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
  const anomalies: Anomaly[] = [];

  for (const project of projects) {
    if (project.budgetCents > 0) {
      const totalDays = countBusinessDays(project.startDate, project.endDate);
      const elapsedDays = countBusinessDays(project.startDate, now < project.endDate ? now : project.endDate);

      if (totalDays > 0 && elapsedDays > 0) {
        const expectedBurnRate = elapsedDays / totalDays;
        const totalCostCents = project.assignments.reduce((sum, assignment) => {
          const assignmentStart = assignment.startDate < project.startDate
            ? project.startDate
            : assignment.startDate;
          const assignmentEnd = assignment.endDate > now ? now : assignment.endDate;
          if (assignmentEnd < assignmentStart) {
            return sum;
          }

          return sum + assignment.dailyCostCents * countBusinessDays(assignmentStart, assignmentEnd);
        }, 0);
        const actualBurnRate = totalCostCents / project.budgetCents;

        if (actualBurnRate > expectedBurnRate * 1.2) {
          const overSpendPercent = Math.round(((actualBurnRate - expectedBurnRate) / expectedBurnRate) * 100);
          anomalies.push({
            type: "budget",
            severity: actualBurnRate > expectedBurnRate * 1.5 ? "critical" : "warning",
            entityId: project.id,
            entityName: project.name,
            message: `Burning budget ${overSpendPercent}% faster than expected. ${Math.round(actualBurnRate * 100)}% spent at ${Math.round(expectedBurnRate * 100)}% timeline.`,
          });
        }
      }
    }

    const upcomingDemands = project.demandRequirements.filter(
      (demand) => demand.startDate <= twoWeeksFromNow && demand.endDate >= now,
    );
    for (const demand of upcomingDemands) {
      const unfilledCount = demand.headcount - demand._count.assignments;
      const unfillPct = demand.headcount > 0 ? unfilledCount / demand.headcount : 0;
      if (unfillPct > 0.3) {
        anomalies.push({
          type: "staffing",
          severity: unfillPct > 0.6 ? "critical" : "warning",
          entityId: project.id,
          entityName: project.name,
          message: `${unfilledCount} of ${demand.headcount} positions unfilled, starting ${demand.startDate.toISOString().slice(0, 10)}.`,
        });
      }
    }

    const overrunAssignments = project.assignments.filter(
      (assignment) => assignment.endDate > project.endDate
        && (assignment.status === "ACTIVE" || assignment.status === "CONFIRMED"),
    );
    if (overrunAssignments.length > 0) {
      anomalies.push({
        type: "timeline",
        severity: "warning",
        entityId: project.id,
        entityName: project.name,
        message: `${overrunAssignments.length} assignment(s) extend beyond the project end date (${project.endDate.toISOString().slice(0, 10)}).`,
      });
    }
  }

  const resourceHoursMap = new Map<string, number>();
  for (const assignment of activeAssignments) {
    const currentHours = resourceHoursMap.get(assignment.resourceId) ?? 0;
    resourceHoursMap.set(assignment.resourceId, currentHours + assignment.hoursPerDay);
  }

  for (const resource of resources) {
    const availability = resource.availability as Record<string, number> | null;
    if (!availability) {
      continue;
    }

    const dailyAvailableHours = Object.values(availability).reduce((sum, hours) => sum + (hours ?? 0), 0) / 5;
    if (dailyAvailableHours <= 0) {
      continue;
    }

    const bookedHours = resourceHoursMap.get(resource.id) ?? 0;
    const utilizationPercent = Math.round((bookedHours / dailyAvailableHours) * 100);

    if (utilizationPercent > 110) {
      anomalies.push({
        type: "utilization",
        severity: utilizationPercent > 130 ? "critical" : "warning",
        entityId: resource.id,
        entityName: resource.displayName,
        message: `Resource at ${utilizationPercent}% utilization (${bookedHours.toFixed(1)}h/${dailyAvailableHours.toFixed(1)}h per day).`,
      });
    } else if (utilizationPercent < 40 && bookedHours > 0) {
      anomalies.push({
        type: "utilization",
        severity: "warning",
        entityId: resource.id,
        entityName: resource.displayName,
        message: `Resource at only ${utilizationPercent}% utilization (${bookedHours.toFixed(1)}h/${dailyAvailableHours.toFixed(1)}h per day).`,
      });
    }
  }

  anomalies.sort((left, right) => {
    if (left.severity !== right.severity) {
      return left.severity === "critical" ? -1 : 1;
    }
    return left.type.localeCompare(right.type);
  });

  return {
    anomalies,
    summary: summarizeAnomalies(anomalies),
  };
}

// ─── Router ──────────────────────────────────────────────────────────────────

export const insightsRouter = createTRPCRouter({
  getAnomalyDetail: controllerProcedure.query(async ({ ctx }) => {
    const snapshot = await buildInsightSnapshot(ctx.db as unknown as InsightsDbAccess);
    return {
      anomalies: snapshot.anomalies,
      count: snapshot.anomalies.length,
    };
  }),

  /**
   * Generate an AI-powered executive narrative for a project.
   * Caches the result in the project's dynamicFields.aiNarrative to avoid
   * calling the AI on every click.
   */
  generateProjectNarrative: controllerProcedure
    .input(z.object({ projectId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const [project, settings] = await Promise.all([
        ctx.db.project.findUnique({
          where: { id: input.projectId },
          include: {
            demandRequirements: {
              select: {
                id: true,
                role: true,
                headcount: true,
                hoursPerDay: true,
                startDate: true,
                endDate: true,
                status: true,
                _count: { select: { assignments: true } },
              },
            },
            assignments: {
              select: {
                id: true,
                role: true,
                hoursPerDay: true,
                startDate: true,
                endDate: true,
                status: true,
                dailyCostCents: true,
                resource: { select: { displayName: true } },
              },
            },
          },
        }),
        ctx.db.systemSettings.findUnique({ where: { id: "singleton" } }),
      ]);

      if (!project) {
        throw new TRPCError({ code: "NOT_FOUND", message: "Project not found" });
      }

      if (!isAiConfigured(settings)) {
        throw new TRPCError({
          code: "PRECONDITION_FAILED",
          message: "AI is not configured. Please set credentials in Admin \u2192 Settings.",
        });
      }

      // Build context data for the prompt
      const now = new Date();
      const totalDays = countBusinessDays(project.startDate, project.endDate);
      const elapsedDays = countBusinessDays(project.startDate, now < project.endDate ? now : project.endDate);
      const progressPercent = totalDays > 0 ? Math.round((elapsedDays / totalDays) * 100) : 0;

      const totalDemandHeadcount = project.demandRequirements.reduce((s, d) => s + d.headcount, 0);
      const filledDemandHeadcount = project.demandRequirements.reduce(
        (s, d) => s + Math.min(d._count.assignments, d.headcount),
        0,
      );
      const staffingPercent = totalDemandHeadcount > 0
        ? Math.round((filledDemandHeadcount / totalDemandHeadcount) * 100)
        : 100;

      // Estimated cost from assignments
      const totalCostCents = project.assignments.reduce((s, a) => {
        const days = countBusinessDays(a.startDate, a.endDate);
        return s + a.dailyCostCents * days;
      }, 0);

      const budgetCents = project.budgetCents;
      const budgetUsedPercent = budgetCents > 0 ? Math.round((totalCostCents / budgetCents) * 100) : 0;

      const overrunAssignments = project.assignments.filter(
        (a) => a.endDate > project.endDate,
      );

      const dataContext = [
        `Project: ${project.name} (${project.shortCode})`,
        `Status: ${project.status}`,
        `Timeline: ${project.startDate.toISOString().slice(0, 10)} to ${project.endDate.toISOString().slice(0, 10)} (${progressPercent}% elapsed)`,
        `Budget: ${(budgetCents / 100).toLocaleString("en-US", { style: "currency", currency: "EUR" })} | Estimated cost: ${(totalCostCents / 100).toLocaleString("en-US", { style: "currency", currency: "EUR" })} (${budgetUsedPercent}% of budget)`,
        `Staffing: ${filledDemandHeadcount}/${totalDemandHeadcount} positions filled (${staffingPercent}%)`,
        `Active assignments: ${project.assignments.filter((a) => a.status === "ACTIVE" || a.status === "CONFIRMED").length}`,
        overrunAssignments.length > 0
          ? `Timeline risk: ${overrunAssignments.length} assignment(s) extend beyond project end date`
          : "No timeline overruns detected",
      ].join("\n");

      const prompt = `Generate a concise executive summary for this project covering: budget status, staffing completeness, timeline risk, and key action items. Be specific with numbers. Keep it to 3-5 sentences.

${dataContext}`;

      const client = createAiClient(settings!);
      const model = settings!.azureOpenAiDeployment!;
      const maxTokens = settings!.aiMaxCompletionTokens ?? 300;
      const temperature = settings!.aiTemperature ?? 1;

      const provider = settings!.aiProvider ?? "openai";
      let narrative = "";
      try {
        const completion = await loggedAiCall(provider, model, prompt.length, () =>
          client.chat.completions.create({
            messages: [
              { role: "system", content: "You are a project management analyst providing brief executive summaries. Be factual and action-oriented." },
              { role: "user", content: prompt },
            ],
            max_completion_tokens: maxTokens,
            model,
            ...(temperature !== 1 ? { temperature } : {}),
          }),
        );
        narrative = completion.choices[0]?.message?.content?.trim() ?? "";
      } catch (err) {
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message: `AI call failed: ${parseAiError(err)}`,
        });
      }

      if (!narrative) {
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message: "AI returned an empty response.",
        });
      }

      const generatedAt = new Date().toISOString();

      // Cache in project dynamicFields
      const existingDynamic = (project.dynamicFields as Record<string, unknown>) ?? {};
      await ctx.db.project.update({
        where: { id: input.projectId },
        data: {
          dynamicFields: {
            ...existingDynamic,
            aiNarrative: narrative,
            aiNarrativeGeneratedAt: generatedAt,
          },
        },
      });

      return { narrative, generatedAt };
    }),

  /**
   * Rule-based anomaly detection across all active projects.
   * No AI involved — pure data analysis.
   */
  detectAnomalies: controllerProcedure.query(async ({ ctx }) => {
    const snapshot = await buildInsightSnapshot(ctx.db as unknown as InsightsDbAccess);
    return snapshot.anomalies;
  }),

  /**
   * Dashboard-friendly summary: anomaly counts by category + total.
   */
  getInsightsSummary: controllerProcedure.query(async ({ ctx }) => {
    const snapshot = await buildInsightSnapshot(ctx.db as unknown as InsightsDbAccess);
    return snapshot.summary;
  }),

  /**
   * Retrieve a cached AI narrative for a project (if one was previously generated).
   */
  getCachedNarrative: controllerProcedure
    .input(z.object({ projectId: z.string() }))
    .query(async ({ ctx, input }) => {
      const project = await ctx.db.project.findUnique({
        where: { id: input.projectId },
        select: { dynamicFields: true },
      });
      if (!project) {
        throw new TRPCError({ code: "NOT_FOUND", message: "Project not found" });
      }
      const df = project.dynamicFields as Record<string, unknown> | null;
      const narrative = (df?.aiNarrative as string) ?? null;
      const generatedAt = (df?.aiNarrativeGeneratedAt as string) ?? null;
      return { narrative, generatedAt };
    }),
});
