import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import {
  buildEvaluationScore,
  canAccessExperienciometro,
  canManageExperienciometro,
  ensureExperienceBaseline,
  getActiveExperiencePolicy,
  getPublishedExperienceTemplate,
  mapExperienceEvaluation,
  mapExperiencePolicy,
  parseRecentWeightsCsv,
  recalculateLocationMetrics,
  signalFromScore,
} from "@/lib/experienciometro";
import type { ExperienceEvaluationCreatePayload } from "@/lib/experienciometro/types";

function parseDate(value: string | undefined): Date | null {
  if (!value) {
    return null;
  }

  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) {
    return null;
  }

  return parsed;
}

function clampScore(raw: number): number {
  const rounded = Math.round(raw);
  if (rounded < 0) {
    return 0;
  }

  if (rounded > 3) {
    return 3;
  }

  return rounded;
}

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

  if (!canAccessExperienciometro(session.user.role)) {
    return NextResponse.json({ error: "Forbidden" }, { status: 403 });
  }

  await ensureExperienceBaseline(prisma, session.user.id);

  const [policyRaw, templateRaw, locationsRaw, usersRaw] = await Promise.all([
    getActiveExperiencePolicy(prisma),
    getPublishedExperienceTemplate(prisma),
    prisma.location.findMany({
      where: { isActive: true },
      select: {
        id: true,
        code: true,
        name: true,
      },
      orderBy: [{ name: "asc" }],
    }),
    prisma.user.findMany({
      where: { status: "active" },
      select: {
        id: true,
        name: true,
        email: true,
      },
      orderBy: [{ name: "asc" }, { email: "asc" }],
      take: 250,
    }),
  ]);

  return NextResponse.json({
    ok: true,
    policy: mapExperiencePolicy(policyRaw),
    template: {
      ...templateRaw,
      categories: templateRaw.categories.map((category) => ({
        ...category,
        items: category.items.filter((item) => item.isActive),
      })),
    },
    locations: locationsRaw,
    users: usersRaw.map((user) => ({
      id: user.id,
      name: user.name?.trim() || user.email,
    })),
  });
}

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

  if (!canManageExperienciometro(session.user.role)) {
    return NextResponse.json({ error: "Forbidden" }, { status: 403 });
  }

  await ensureExperienceBaseline(prisma, session.user.id);

  const body = (await request.json()) as ExperienceEvaluationCreatePayload;
  const locationId = body.locationId?.trim();
  if (!locationId) {
    return NextResponse.json({ error: "locationId is required." }, { status: 400 });
  }

  if (!Array.isArray(body.responses) || body.responses.length === 0) {
    return NextResponse.json({ error: "responses are required." }, { status: 400 });
  }

  const evaluatedAt = parseDate(body.evaluatedAt) ?? new Date();

  const [location, policy, template] = await Promise.all([
    prisma.location.findFirst({
      where: {
        id: locationId,
        isActive: true,
      },
      select: { id: true },
    }),
    getActiveExperiencePolicy(prisma),
    body.templateId
      ? prisma.experienceTemplate.findFirst({
          where: {
            id: body.templateId,
          },
          include: {
            categories: {
              include: {
                items: {
                  orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
                },
              },
              orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
            },
          },
        })
      : getPublishedExperienceTemplate(prisma),
  ]);

  if (!location) {
    return NextResponse.json({ error: "Location not found." }, { status: 404 });
  }

  if (!template) {
    return NextResponse.json({ error: "Template not found." }, { status: 404 });
  }

  const activeItems = template.categories.flatMap((category) =>
    category.items
      .filter((item) => item.isActive)
      .map((item) => ({
        categoryId: category.id,
        categoryKey: category.key,
        categoryName: category.name,
        categoryWeight: category.weight,
        itemId: item.id,
        itemKey: item.key,
        itemLabel: item.label,
        itemWeight: item.weight,
        requiresObservation: item.requiresObservation,
      }))
  );

  const responseByItemId = new Map(body.responses.map((response) => [response.itemId, response]));
  const missingItem = activeItems.find((item) => !responseByItemId.has(item.itemId));
  if (missingItem) {
    return NextResponse.json({ error: `Missing response for item ${missingItem.itemLabel}.` }, { status: 400 });
  }

  const scoreInputs = activeItems.map((item) => {
    const payload = responseByItemId.get(item.itemId)!;
    const score = clampScore(payload.score);

    return {
      categoryId: item.categoryId,
      categoryKey: item.categoryKey,
      categoryName: item.categoryName,
      categoryWeight: item.categoryWeight,
      itemId: item.itemId,
      itemWeight: item.itemWeight,
      score,
      comment: payload.comment?.trim() || null,
      observation: payload.observation?.trim() || null,
      createFinding: Boolean(payload.createFinding),
      findingTitle: payload.findingTitle?.trim() || null,
      findingDescription: payload.findingDescription?.trim() || null,
      findingPriority: payload.findingPriority ?? "medium",
      responsibleUserId: payload.responsibleUserId?.trim() || null,
      requiresObservation: item.requiresObservation,
      itemLabel: item.itemLabel,
    };
  });

  const evaluationScore = buildEvaluationScore(
    scoreInputs.map((item) => ({
      categoryId: item.categoryId,
      categoryKey: item.categoryKey,
      categoryName: item.categoryName,
      categoryWeight: item.categoryWeight,
      itemId: item.itemId,
      itemWeight: item.itemWeight,
      score: item.score,
    }))
  );

  const signal = signalFromScore(evaluationScore.totalScorePct, {
    greenThreshold: policy.greenThreshold,
    yellowThreshold: policy.yellowThreshold,
  });

  const requiredObservationMissing = scoreInputs.find(
    (item) => item.requiresObservation && item.score <= 1 && (!item.observation || item.observation.length === 0)
  );

  if (requiredObservationMissing) {
    return NextResponse.json(
      { error: `Observation is required for item ${requiredObservationMissing.itemLabel} when score is low.` },
      { status: 400 }
    );
  }

  const allResponsibleIds = Array.from(
    new Set(scoreInputs.map((item) => item.responsibleUserId).filter((value): value is string => Boolean(value)))
  );

  if (allResponsibleIds.length > 0) {
    const users = await prisma.user.findMany({
      where: {
        id: { in: allResponsibleIds },
      },
      select: { id: true },
    });
    const valid = new Set(users.map((user) => user.id));
    const invalid = allResponsibleIds.find((id) => !valid.has(id));
    if (invalid) {
      return NextResponse.json({ error: "Invalid responsibleUserId provided." }, { status: 400 });
    }
  }

  const created = await prisma.$transaction(async (tx) => {
    const evaluation = await tx.experienceEvaluation.create({
      data: {
        locationId,
        templateId: template.id,
        createdById: session.user.id,
        evaluatedAt,
        generalObservations: body.generalObservations?.trim() || "",
        strengths: body.strengths?.trim() || "",
        improvementAreas: body.improvementAreas?.trim() || "",
        totalScore: evaluationScore.totalScorePct,
        signal,
      },
    });

    await tx.experienceEvaluationResponse.createMany({
      data: scoreInputs.map((item) => ({
        evaluationId: evaluation.id,
        categoryId: item.categoryId,
        itemId: item.itemId,
        score: item.score,
        scorePct: (item.score / 3) * 100,
        comment: item.comment,
        observation: item.observation,
        hasObservation: Boolean(item.observation),
        categoryLabelSnapshot: item.categoryName,
        itemLabelSnapshot: item.itemLabel,
        categoryWeightSnapshot: item.categoryWeight,
        itemWeightSnapshot: item.itemWeight,
      })),
      skipDuplicates: true,
    });

    const findingsToCreate = scoreInputs
      .filter((item) => item.createFinding || Boolean(item.observation))
      .map((item) => ({
        locationId,
        evaluationId: evaluation.id,
        createdById: session.user.id,
        responsibleUserId: item.responsibleUserId,
        title: item.findingTitle || `Observación: ${item.itemLabel}`,
        description: item.findingDescription || item.observation || item.comment || "",
        categoryKey: item.categoryKey,
        categoryLabel: item.categoryName,
        priority: item.findingPriority,
        status: "open" as const,
      }));

    if (findingsToCreate.length > 0) {
      await tx.experienceFinding.createMany({
        data: findingsToCreate,
      });
    }

    await recalculateLocationMetrics(tx, locationId);

    return tx.experienceEvaluation.findUniqueOrThrow({
      where: { id: evaluation.id },
      include: {
        createdBy: {
          select: {
            name: true,
            email: true,
          },
        },
        responses: {
          orderBy: [{ createdAt: "asc" }],
        },
      },
    });
  });

  const latestMetric = await prisma.experienceLocationMetric.findUnique({
    where: { locationId },
    select: {
      currentScore: true,
      previousScore: true,
      trendDelta: true,
      trendDirection: true,
      signal: true,
      totalEvaluations: true,
      openFindings: true,
      lastEvaluationAt: true,
    },
  });

  return NextResponse.json({
    ok: true,
    policy: {
      ...mapExperiencePolicy(policy),
      recentWeights: parseRecentWeightsCsv(policy.recentWeightsCsv),
    },
    evaluation: mapExperienceEvaluation(created),
    metrics: latestMetric,
  });
}
