import { ExperienceTemplateState } from "@prisma/client";
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import {
  canAccessExperienciometro,
  canConfigureExperienciometro,
  ensureExperienceBaseline,
  mapExperienceTemplate,
} from "@/lib/experienciometro";
import type { ExperienceTemplateSavePayload } from "@/lib/experienciometro/types";

function sanitizeKey(value: string): string {
  return value
    .trim()
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "_")
    .replace(/^_+|_+$/g, "")
    .slice(0, 64);
}

function toTemplateState(value: string | undefined): ExperienceTemplateState | null {
  if (value === "draft" || value === "published" || value === "archived") {
    return value;
  }

  return null;
}

export async function GET(_request: Request, context: { params: { id: string } }) {
  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 template = await prisma.experienceTemplate.findUnique({
    where: { id: context.params.id },
    include: {
      categories: {
        include: {
          items: {
            orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
          },
        },
        orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
      },
    },
  });

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

  return NextResponse.json({
    ok: true,
    template: mapExperienceTemplate(template),
  });
}

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

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

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

  const templateId = context.params.id;
  const existing = await prisma.experienceTemplate.findUnique({
    where: { id: templateId },
    select: {
      id: true,
      isDefault: true,
      state: true,
    },
  });

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

  const body = (await request.json()) as ExperienceTemplateSavePayload;
  const state = body.state ? toTemplateState(body.state) : null;
  if (body.state && !state) {
    return NextResponse.json({ error: "Invalid template state." }, { status: 400 });
  }

  const categories = body.categories;

  await prisma.$transaction(async (tx) => {
    if (Array.isArray(categories)) {
      await tx.experienceTemplateCategory.deleteMany({
        where: {
          templateId,
        },
      });

      for (const [categoryIndex, category] of categories.entries()) {
        const categoryName = category.name.trim();
        if (!categoryName) {
          throw new Error("Category name is required.");
        }

        const createdCategory = await tx.experienceTemplateCategory.create({
          data: {
            templateId,
            key: sanitizeKey(category.key || category.name || `categoria_${categoryIndex + 1}`),
            name: categoryName,
            weight: category.weight && category.weight > 0 ? category.weight : 1,
            sortOrder: Number.isFinite(category.sortOrder ?? NaN) ? Number(category.sortOrder) : categoryIndex,
          },
        });

        if (!Array.isArray(category.items) || category.items.length === 0) {
          throw new Error(`Category '${categoryName}' must have at least one item.`);
        }

        await tx.experienceTemplateItem.createMany({
          data: category.items.map((item, itemIndex) => {
            const itemLabel = item.label.trim();
            if (!itemLabel) {
              throw new Error(`Category '${categoryName}' has an item without label.`);
            }

            return {
              categoryId: createdCategory.id,
              key: sanitizeKey(item.key || item.label || `item_${itemIndex + 1}`),
              label: itemLabel,
              weight: item.weight && item.weight > 0 ? item.weight : 1,
              sortOrder: Number.isFinite(item.sortOrder ?? NaN) ? Number(item.sortOrder) : itemIndex,
              allowsComment: item.allowsComment ?? true,
              requiresObservation: item.requiresObservation ?? false,
              allowsEvidence: item.allowsEvidence ?? true,
              isActive: item.isActive ?? true,
            };
          }),
        });
      }
    }

    const shouldPublish = Boolean(body.publish);
    const nextIsDefault = typeof body.isDefault === "boolean" ? body.isDefault : existing.isDefault;
    const nextState = shouldPublish ? ExperienceTemplateState.published : state ?? existing.state;

    if (shouldPublish) {
      await tx.experienceTemplate.updateMany({
        where: {
          id: { not: templateId },
          state: ExperienceTemplateState.published,
        },
        data: {
          state: ExperienceTemplateState.archived,
        },
      });
    }

    if (nextIsDefault) {
      await tx.experienceTemplate.updateMany({
        where: {
          id: { not: templateId },
          isDefault: true,
        },
        data: {
          isDefault: false,
        },
      });
    }

    await tx.experienceTemplate.update({
      where: { id: templateId },
      data: {
        ...(typeof body.name === "string" ? { name: body.name.trim() || existing.id } : {}),
        isDefault: nextIsDefault,
        state: nextState,
        publishedAt: shouldPublish ? new Date() : nextState === ExperienceTemplateState.published ? new Date() : null,
      },
    });
  });

  const template = await prisma.experienceTemplate.findUniqueOrThrow({
    where: { id: templateId },
    include: {
      categories: {
        include: {
          items: {
            orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
          },
        },
        orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
      },
    },
  });

  return NextResponse.json({
    ok: true,
    template: mapExperienceTemplate(template),
  });
}
