import { db } from "@/app/db/db";
import { NextRequest, NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
import {
  portfolio,
  portfolioInvitations,
  portfolioUsers,
} from "@/app/db/schema/portfolio";
import { user } from "@/app/db/schema/users";
import { and, eq } from "drizzle-orm";
import { z } from "zod";
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { normaliseEmail } from "@/app/lib/email";
import { planInvitationApplication } from "@/app/lib/portfolioInvitations";

// GET: list pending portfolio invitations addressed to the current user's
// email, across all portfolios. Used by the profile-menu notifications panel.
export async function GET() {
  const session = await getServerSession(AuthOptions);
  if (!session?.user?.dbId || !session.user.email) {
    return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
  }
  const email = normaliseEmail(session.user.email);

  try {
    const rows = await db
      .select({
        id: portfolioInvitations.id,
        portfolioId: portfolioInvitations.portfolioId,
        portfolioName: portfolio.name,
        role: portfolioInvitations.role,
        invitedByName: user.firstName,
        invitedByEmail: user.email,
        createdAt: portfolioInvitations.createdAt,
      })
      .from(portfolioInvitations)
      .innerJoin(portfolio, eq(portfolio.id, portfolioInvitations.portfolioId))
      .leftJoin(user, eq(user.id, portfolioInvitations.invitedByUserId))
      .where(eq(portfolioInvitations.email, email));

    return NextResponse.json(
      {
        invitations: rows.map((r) => ({
          invitationId: r.id.toString(),
          portfolioId: r.portfolioId.toString(),
          portfolioName: r.portfolioName,
          role: r.role,
          invitedByName: r.invitedByName ?? r.invitedByEmail ?? null,
          createdAt: r.createdAt.toISOString(),
        })),
      },
      { status: 200 },
    );
  } catch (err) {
    console.error("GET /user/invitations error:", err);
    return NextResponse.json(
      { error: "Failed to fetch invitations" },
      { status: 500 },
    );
  }
}

// POST: accept or decline an invitation addressed to the current user.
//   { invitationId, action: "accept" | "decline" }
//
// Accept: writes the portfolioUsers row (skipped if already a member) and
// deletes the invitation atomically.
// Decline: deletes the invitation. Silent — no inviter notification.
export async function POST(req: NextRequest) {
  const session = await getServerSession(AuthOptions);
  if (!session?.user?.dbId || !session.user.email) {
    return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
  }
  const sessionEmail = normaliseEmail(session.user.email);
  const sessionUserId = BigInt(session.user.dbId);

  const bodySchema = z.object({
    invitationId: z.string(),
    action: z.enum(["accept", "decline"]),
  });
  let body: z.infer<typeof bodySchema>;
  try {
    body = bodySchema.parse(await req.json());
  } catch {
    return NextResponse.json({ error: "Invalid body" }, { status: 400 });
  }

  try {
    const invId = BigInt(body.invitationId);

    const [invitation] = await db
      .select({
        id: portfolioInvitations.id,
        portfolioId: portfolioInvitations.portfolioId,
        email: portfolioInvitations.email,
        role: portfolioInvitations.role,
      })
      .from(portfolioInvitations)
      .where(eq(portfolioInvitations.id, invId))
      .limit(1);

    if (!invitation) {
      return NextResponse.json(
        { error: "Invitation not found" },
        { status: 404 },
      );
    }
    if (invitation.email !== sessionEmail) {
      // Either someone else's invitation or address mismatch — treat as
      // not-found so we don't leak existence of other users' invitations.
      return NextResponse.json(
        { error: "Invitation not found" },
        { status: 404 },
      );
    }

    if (body.action === "decline") {
      await db
        .delete(portfolioInvitations)
        .where(eq(portfolioInvitations.id, invId));
      console.log("INVITATION_DECLINED", {
        email: sessionEmail,
        invitationId: body.invitationId,
        portfolioId: invitation.portfolioId.toString(),
      });
      return NextResponse.json(
        { success: true, action: "declined" },
        { status: 200 },
      );
    }

    // Accept: load existing memberships so we don't double-insert, then
    // delegate to the shared planning function.
    const existing = await db
      .select({ portfolioId: portfolioUsers.portfolioId })
      .from(portfolioUsers)
      .where(eq(portfolioUsers.userId, sessionUserId));

    const plan = planInvitationApplication({
      userId: sessionUserId,
      invitations: [
        {
          id: invitation.id,
          portfolioId: invitation.portfolioId,
          role: invitation.role as "creator" | "admin" | "read" | "write",
        },
      ],
      existingPortfolioIds: new Set(existing.map((m) => m.portfolioId)),
    });

    await db.transaction(async (tx) => {
      if (plan.memberships.length > 0) {
        await tx.insert(portfolioUsers).values(plan.memberships);
      }
      for (const id of plan.invitationsToDelete) {
        await tx
          .delete(portfolioInvitations)
          .where(eq(portfolioInvitations.id, id));
      }
    });

    console.log("INVITATION_ACCEPTED", {
      email: sessionEmail,
      invitationId: body.invitationId,
      portfolioId: invitation.portfolioId.toString(),
      newMembership: plan.memberships.length > 0,
    });

    // /home renders the user's portfolio list from the DB in a server
    // component; invalidate so the next navigation there picks up the new
    // membership. router.refresh() handles the in-place case client-side.
    revalidatePath("/home");

    return NextResponse.json(
      {
        success: true,
        action: "accepted",
        portfolioId: invitation.portfolioId.toString(),
      },
      { status: 200 },
    );
  } catch (err) {
    console.error("POST /user/invitations error:", err);
    return NextResponse.json(
      { error: "Failed to update invitation" },
      { status: 500 },
    );
  }
}
