import { NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import { prisma } from "@/lib/prisma";
import { requireRole } from "@/lib/auth";
import { generateInstitutionCode } from "@/lib/institution-code";
import { generatePassword, sendCentralMail } from "@/lib/central-mail";
import { appBaseUrl, demoAccessEmail } from "@/lib/email-templates";
import { clampDemoValidDays, demoExpiryFrom } from "@/lib/demo-access";
import type { InstitutionType } from "@prisma/client";

type Params = { params: Promise<{ id: string }> };

const STATUSES = ["PENDING", "CONTACTED", "CONVERTED", "REJECTED"] as const;

/** landing-page demo audience → concrete institution type for the demo environment */
const DEMO_TYPE_TO_INSTITUTION: Record<string, InstitutionType> = {
  KG_PRIMARY: "PRIMARY_AND_HIGH_SCHOOL",
  HIGH_SCHOOL: "PRIMARY_AND_HIGH_SCHOOL",
  PRIMARY_HIGH: "PRIMARY_AND_HIGH_SCHOOL",
  COLLEGE_UNI: "COLLEGE_AND_UNIVERSITY",
  COACHING: "COACHING",
  MADRASHA: "MADRASHA",
};

const DEMO_TYPE_LABELS: Record<string, string> = {
  KG_PRIMARY: "KG & Primary",
  HIGH_SCHOOL: "High School",
  PRIMARY_HIGH: "Primary & High School",
  COLLEGE_UNI: "College & University",
  COACHING: "Coaching Center",
  MADRASHA: "Madrasha",
};

/**
 * Grant (or re-grant) demo access: provision a demo institution with this
 * requester as its admin, then email the credentials. Re-grants rotate the
 * demo password instead of creating a duplicate environment. The demo stays
 * usable for `validDays` days (central-admin set, default 10) — afterwards
 * every login of the demo institution is blocked.
 */
async function grantDemoAccess(demoId: number, validDaysInput?: unknown) {
  const demo = await prisma.demoRequest.findUnique({ where: { id: demoId } });
  if (!demo) return { error: "NOT_FOUND" as const };

  const validDays = clampDemoValidDays(validDaysInput ?? demo.validDays, demo.validDays);
  const grantedAt = new Date();
  const demoExpiresAt = demoExpiryFrom(grantedAt, validDays);

  const password = generatePassword(10);
  const loginUrl = `${appBaseUrl()}/login`;
  const type = DEMO_TYPE_TO_INSTITUTION[demo.institutionType] ?? "HIGH_SCHOOL";
  const typeLabel = DEMO_TYPE_LABELS[demo.institutionType] ?? null;

  const existingUser = demo.demoUserId
    ? await prisma.user.findUnique({
        where: { id: demo.demoUserId },
        select: { id: true, institutionId: true },
      })
    : null;

  let code: string;
  if (existingUser) {
    // re-send: rotate the password on the existing demo admin and restart
    // the validity window from now
    await prisma.user.update({
      where: { id: existingUser.id },
      data: { passwordHash: await bcrypt.hash(password, 10) },
    });
    const inst = await prisma.institution.findUnique({
      where: { id: existingUser.institutionId ?? -1 },
      select: { code: true },
    });
    code = inst?.code ?? "—";
    await prisma.demoRequest.update({
      where: { id: demo.id },
      data: { validDays, demoExpiresAt, demoSentAt: grantedAt },
    });
  } else {
    // first grant: a fresh demo environment for this requester
    const emailTaken =
      (await prisma.user.findUnique({ where: { email: demo.email }, select: { id: true } })) ??
      (await prisma.institution.findUnique({ where: { email: demo.email }, select: { id: true } }));
    if (emailTaken) return { error: "EMAIL_TAKEN" as const };

    code = await prisma.$transaction(async (tx) => {
      const instCode = await generateInstitutionCode(type);
      const institution = await tx.institution.create({
        data: {
          name: `${demo.institutionName} (Demo)`,
          code: instCode,
          type,
          email: demo.email,
          phone: demo.phone ?? "—",
          address: "—",
          status: "ACTIVE",
        },
      });
      const user = await tx.user.create({
        data: {
          name: demo.name,
          email: demo.email,
          passwordHash: await bcrypt.hash(password, 10),
          role: "INSTITUTION_ADMIN",
          institutionId: institution.id,
        },
        select: { id: true },
      });
      await tx.demoRequest.update({
        where: { id: demo.id },
        data: {
          demoUserId: user.id,
          demoInstitutionId: institution.id,
          validDays,
          demoExpiresAt,
          demoSentAt: grantedAt,
        },
      });
      return instCode;
    });
  }

  if (demo.status === "PENDING") {
    await prisma.demoRequest.update({
      where: { id: demo.id },
      data: { status: "CONTACTED", contactedAt: new Date() },
    });
  }

  const tpl = demoAccessEmail({
    toName: demo.name,
    institutionName: demo.institutionName,
    institutionTypeLabel: typeLabel,
    email: demo.email,
    password,
    code,
    loginUrl,
    validDays,
    expiresAt: demoExpiresAt.toLocaleDateString("en-GB", {
      day: "numeric",
      month: "long",
      year: "numeric",
    }),
  });

  const sent = await sendCentralMail({
    to: demo.email,
    subject: tpl.subject,
    html: tpl.html,
    text: tpl.text,
  });

  const updated = await prisma.demoRequest.update({
    where: { id: demo.id },
    data: { demoSentAt: grantedAt },
  });
  return { demo: updated, sent, password };
}

export async function PATCH(req: Request, { params }: Params) {
  const session = await requireRole("SUPER_ADMIN");
  if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const { id } = await params;
  try {
    const body = await req.json();

    if (body.sendDemo) {
      const result = await grantDemoAccess(Number(id), body.validDays);
      if ("error" in result) {
        const status = result.error === "NOT_FOUND" ? 404 : 409;
        return NextResponse.json({ error: result.error }, { status });
      }
      return NextResponse.json(result);
    }

    // Adjust the validity window. For an already-granted demo the expiry is
    // recomputed — extending the days re-opens an expired demo from now.
    if (body.validDays !== undefined) {
      const demo = await prisma.demoRequest.findUnique({ where: { id: Number(id) } });
      if (!demo) return NextResponse.json({ error: "NOT_FOUND" }, { status: 404 });

      const validDays = clampDemoValidDays(body.validDays, demo.validDays);
      if (validDays < 1) {
        return NextResponse.json({ error: "INVALID_DAYS" }, { status: 400 });
      }
      const demoExpiresAt = demo.demoSentAt
        ? demoExpiryFrom(demo.demoSentAt, validDays)
        : null;
      const updated = await prisma.demoRequest.update({
        where: { id: demo.id },
        data: { validDays, demoExpiresAt },
      });
      return NextResponse.json({ demo: updated });
    }

    const status = String(body.status ?? "");
    if (!STATUSES.includes(status as never)) {
      return NextResponse.json({ error: "Invalid status" }, { status: 400 });
    }
    const adminNote =
      body.adminNote === undefined || body.adminNote === null
        ? undefined
        : String(body.adminNote).trim().slice(0, 500) || null;

    const demo = await prisma.demoRequest.update({
      where: { id: Number(id) },
      data: {
        status: status as never,
        adminNote,
        // first follow-up stamps the contact time; a decision stamps the close time
        ...(status === "CONTACTED" ? { contactedAt: new Date() } : {}),
        ...(status === "CONVERTED" || status === "REJECTED" ? { decidedAt: new Date() } : {}),
      },
    });
    return NextResponse.json({ demo });
  } catch {
    return NextResponse.json({ error: "Could not update demo request" }, { status: 500 });
  }
}

export async function DELETE(_req: Request, { params }: Params) {
  const session = await requireRole("SUPER_ADMIN");
  if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const { id } = await params;
  try {
    await prisma.demoRequest.delete({ where: { id: Number(id) } });
    return NextResponse.json({ ok: true });
  } catch {
    return NextResponse.json({ error: "Could not delete demo request" }, { status: 500 });
  }
}
