import { NextResponse } from "next/server";
import { createHash } from "crypto";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { HrPayrollLineType } from "@prisma/client";
import { prisma } from "@/lib/prisma";
import { canManageHumanCapital } from "@/lib/human-capital";

const PAYROLL_LINE_TYPES = new Set(Object.values(HrPayrollLineType));

function parsePayrollLineType(value: string): HrPayrollLineType | null {
  const normalized = value.trim().toLowerCase();
  if (!normalized) {
    return null;
  }
  return PAYROLL_LINE_TYPES.has(normalized as HrPayrollLineType) ? (normalized as HrPayrollLineType) : null;
}

function parseCsvLine(line: string): string[] {
  const result: string[] = [];
  let current = "";
  let inQuotes = false;

  for (let i = 0; i < line.length; i += 1) {
    const char = line[i];
    if (char === '"') {
      if (inQuotes && line[i + 1] === '"') {
        current += '"';
        i += 1;
      } else {
        inQuotes = !inQuotes;
      }
      continue;
    }

    if (char === "," && !inQuotes) {
      result.push(current.trim());
      current = "";
      continue;
    }

    current += char;
  }

  result.push(current.trim());
  return result;
}

function parseCsv(text: string) {
  const lines = text
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter((line) => line.length > 0);

  if (lines.length < 2) {
    return { header: [], rows: [] };
  }

  const header = parseCsvLine(lines[0]).map((value) => value.toLowerCase());
  const rows = lines.slice(1).map((line) => parseCsvLine(line));
  return { header, rows };
}

export async function POST(request: Request) {
  const session = await getServerSession(authOptions);
  if (!session?.user) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  if (!canManageHumanCapital(session.user.role, session.user.department)) {
    return NextResponse.json({ error: "Only owner/HC leaders can import payroll." }, { status: 403 });
  }

  const body = (await request.json().catch(() => ({}))) as {
    csvText?: string;
    periodStart?: string;
    periodEnd?: string;
    frequency?: "weekly" | "biweekly";
    locationCode?: string | null;
    externalRef?: string | null;
    sourceFileName?: string | null;
  };

  if (!body.csvText || !body.periodStart || !body.periodEnd || !body.frequency) {
    return NextResponse.json({ error: "csvText, periodStart, periodEnd, and frequency are required." }, { status: 400 });
  }

  const { header, rows } = parseCsv(body.csvText);
  const requiredColumns = ["personidentifier", "linetype", "concept", "amount"];
  for (const column of requiredColumns) {
    if (!header.includes(column)) {
      return NextResponse.json({ error: `Missing column: ${column}` }, { status: 400 });
    }
  }

  const hash = createHash("sha256")
    .update(body.csvText)
    .update(body.periodStart)
    .update(body.periodEnd)
    .update(body.frequency)
    .digest("hex");

  const existingRun = await prisma.hrPayrollRun.findFirst({
    where: { rawHash: hash },
  });

  if (existingRun) {
    return NextResponse.json({ ok: true, runId: existingRun.id, reused: true });
  }

  const importJob = await prisma.hrPayrollImportJob.create({
    data: {
      department: "capital_humano",
      status: "queued",
      sourceFileName: body.sourceFileName ?? null,
      requestHash: hash,
      triggeredById: session.user.id,
    },
  });

  const columnIndex = new Map<string, number>();
  header.forEach((column, idx) => columnIndex.set(column, idx));

  const parsedRows = rows
    .map((values) => {
      const personIdentifier = values[columnIndex.get("personidentifier") ?? -1] ?? "";
      const lineTypeRaw = values[columnIndex.get("linetype") ?? -1] ?? "";
      const lineType = parsePayrollLineType(lineTypeRaw);
      const concept = values[columnIndex.get("concept") ?? -1] ?? "";
      const amountRaw = values[columnIndex.get("amount") ?? -1] ?? "";
      const locationCode = values[columnIndex.get("locationcode") ?? -1] ?? null;
      const userId = values[columnIndex.get("userid") ?? -1] ?? null;
      const amount = Number(amountRaw.replace(/[^0-9.-]+/g, ""));

      return {
        personIdentifier,
        lineType,
        concept,
        amount,
        locationCode: locationCode || null,
        userId: userId || null,
      };
    })
    .filter((row) => row.personIdentifier && row.lineType && row.concept && Number.isFinite(row.amount));

  const validRows = parsedRows.filter(
    (row): row is typeof row & { lineType: HrPayrollLineType } =>
      Boolean(row.personIdentifier) && row.lineType !== null && Boolean(row.concept) && Number.isFinite(row.amount)
  );

  if (validRows.length === 0) {
    await prisma.hrPayrollImportJob.update({
      where: { id: importJob.id },
      data: {
        status: "failed",
        message: "No valid rows parsed.",
        rowsRead: rows.length,
        failedRows: rows.length,
        completedAt: new Date(),
      },
    });
    return NextResponse.json({ error: "No valid rows parsed." }, { status: 400 });
  }

  const run = await prisma.hrPayrollRun.create({
    data: {
      department: "capital_humano",
      source: "contpaqi_csv",
      externalRef: body.externalRef ?? null,
      periodStart: new Date(body.periodStart),
      periodEnd: new Date(body.periodEnd),
      frequency: body.frequency,
      locationCode: body.locationCode ?? null,
      rawHash: hash,
      importedById: session.user.id,
    },
  });

  const lines = validRows.map((row) => ({
    runId: run.id,
    userId: row.userId,
    personIdentifier: row.personIdentifier,
    lineType: row.lineType,
    concept: row.concept,
    amount: row.amount,
    locationCode: row.locationCode ?? body.locationCode ?? null,
  }));

  await prisma.hrPayrollLine.createMany({ data: lines });

  await prisma.hrPayrollImportJob.update({
    where: { id: importJob.id },
    data: {
      status: "success",
      rowsRead: rows.length,
      rowsImported: lines.length,
      failedRows: rows.length - lines.length,
      runId: run.id,
      completedAt: new Date(),
    },
  });

  return NextResponse.json({ ok: true, runId: run.id, rowsImported: lines.length });
}
