import { db } from "@/app/db/db";
import { NextRequest, NextResponse } from "next/server";
import { portfolioTag } from "@/app/db/schema/tags";
import { and, eq, ne } from "drizzle-orm";
import { z } from "zod";
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { validateTagDraft } from "@/lib/tags/model";
import {
  canWriteTags,
  getPortfolioRole,
  isUniqueViolation,
} from "@/lib/tags/server";

const editSchema = z.object({ name: z.string(), colour: z.string() });

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

async function authWrite(portfolioId: string, tagId: string, email: string | undefined) {
  if (!email) return { error: "Unauthorised", status: 401 as const };
  if (!/^\d+$/.test(portfolioId) || !/^\d+$/.test(tagId)) {
    return { error: "Invalid id", status: 400 as const };
  }
  const role = await getPortfolioRole(BigInt(portfolioId), email);
  if (!canWriteTags(role)) return { error: "Forbidden", status: 403 as const };
  return { ok: true as const };
}

/** PATCH — rename / recolour a tag (write-gated). */
export async function PATCH(req: NextRequest, props: Params) {
  const session = await getServerSession(AuthOptions);
  const { portfolioId, tagId } = await props.params;
  const auth = await authWrite(portfolioId, tagId, session?.user?.email);
  if ("error" in auth) {
    return NextResponse.json({ error: auth.error }, { status: auth.status });
  }
  const pId = BigInt(portfolioId);
  const tId = BigInt(tagId);

  let body: z.infer<typeof editSchema>;
  try {
    body = editSchema.parse(await req.json());
  } catch {
    return NextResponse.json({ error: "Invalid body" }, { status: 400 });
  }

  // Uniqueness is checked against the OTHER tags, so a tag may keep/recase its
  // own name (ADR-0013).
  const others = await db
    .select({ name: portfolioTag.name })
    .from(portfolioTag)
    .where(and(eq(portfolioTag.portfolioId, pId), ne(portfolioTag.id, tId)));

  const result = validateTagDraft(body, others.map((o) => o.name));
  if (!result.ok) {
    return NextResponse.json({ error: result.error }, { status: 400 });
  }

  // The `others` pre-check is racy — two concurrent renames to the same name
  // both pass it — so the case-insensitive unique index is the real guard.
  // Mirror POST: a unique violation is a clean 409, not an uncaught 500.
  let updated;
  try {
    updated = await db
      .update(portfolioTag)
      .set({ name: result.tag.name, colour: result.tag.colour })
      .where(and(eq(portfolioTag.id, tId), eq(portfolioTag.portfolioId, pId)))
      .returning({ id: portfolioTag.id });
  } catch (err) {
    if (isUniqueViolation(err)) {
      return NextResponse.json(
        { error: "A tag with this name already exists" },
        { status: 409 },
      );
    }
    console.error("PATCH /tags update error:", err);
    return NextResponse.json({ error: "Couldn't save the tag" }, { status: 500 });
  }

  if (updated.length === 0) {
    return NextResponse.json({ error: "Tag not found" }, { status: 404 });
  }
  return NextResponse.json({
    id: tId.toString(),
    name: result.tag.name,
    colour: result.tag.colour,
  });
}

/** DELETE — remove a tag; memberships cascade (ADR-0013). */
export async function DELETE(_req: NextRequest, props: Params) {
  const session = await getServerSession(AuthOptions);
  const { portfolioId, tagId } = await props.params;
  const auth = await authWrite(portfolioId, tagId, session?.user?.email);
  if ("error" in auth) {
    return NextResponse.json({ error: auth.error }, { status: auth.status });
  }

  const deleted = await db
    .delete(portfolioTag)
    .where(
      and(
        eq(portfolioTag.id, BigInt(tagId)),
        eq(portfolioTag.portfolioId, BigInt(portfolioId)),
      ),
    )
    .returning({ id: portfolioTag.id });

  if (deleted.length === 0) {
    return NextResponse.json({ error: "Tag not found" }, { status: 404 });
  }
  return NextResponse.json({ success: true });
}
