import { db } from "@/app/db/db";
import { NextRequest, NextResponse } from "next/server";
import {
  portfolio,
  portfolioInvitations,
  portfolioUsers,
} from "@/app/db/schema/portfolio";
import { user } from "@/app/db/schema/users";
import {
  recommendation,
  recommendationMaterials,
  planRecommendations,
  plan,
  scenario,
} from "@/app/db/schema/recommendations";
import {
  propertyTargets,
  propertyDetailsEpc,
  property,
} from "@/app/db/schema/property";
import { and, eq, inArray, Name } from "drizzle-orm";
import { z } from "zod";
import { ROLE_OPTIONS } from "@/app/portfolio/[slug]/(portfolio)/settings/roles";
import { normaliseEmail } from "@/app/lib/email";
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { PortfolioInvitationEmail } from "@/app/email_templates/portfolio_invitation";
import {
  denyIfNotAdmin,
  resolvePortfolioPrivilege,
} from "@/app/lib/resolvePortfolioPrivilege";
import { inviteRequestSchema } from "./inviteRequestSchema";

// Get collaborators (users) that have access to the portfolio, plus the
// effective privilege of the requesting user (so the UI knows which actions
// to expose).
export async function GET(
  _req: NextRequest,
  props: { params: Promise<{ portfolioId: string }> }
) {
  const { portfolioId } = await props.params;

  const session = await getServerSession(AuthOptions);
  if (!session?.user?.dbId || !session.user.email) {
    return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
  }

  try {
    const pId = BigInt(portfolioId);

    const rows = await db
      .select({
        portfolioUserId: portfolioUsers.id,
        userId: portfolioUsers.userId,
        role: portfolioUsers.role,
        name: user.firstName,
        email: user.email,
      })
      .from(portfolioUsers)
      .leftJoin(user, eq(user.id, portfolioUsers.userId))
      .where(eq(portfolioUsers.portfolioId, pId));

    const collaborators = rows.map((r) => ({
      portfolioUserId: r.portfolioUserId ? r.portfolioUserId.toString() : null,
      userId: r.userId ? r.userId.toString() : null,
      role: r.role,
      name: r.name ?? null,
      email: r.email ?? "",
    }));

    const privilege = await resolvePortfolioPrivilege({
      portfolioId: pId,
      userId: BigInt(session.user.dbId),
      userEmail: session.user.email,
    });

    return NextResponse.json(
      { users: collaborators, currentUser: { privilege } },
      { status: 200 },
    );
  } catch (err) {
    console.error("GET /users error:", err);
    return NextResponse.json(
      { error: "Failed to fetch users" },
      { status: 500 }
    );
  }
}

// DELETE: remove a collaborator from this portfolio.
export async function DELETE(
  req: NextRequest,
  props: { params: Promise<{ portfolioId: string }> }
) {
  const { portfolioId } = await props.params;

  const session = await getServerSession(AuthOptions);
  const denied = await denyIfNotAdmin(BigInt(portfolioId), session);
  if (denied) return denied;

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

  try {
    const pId = BigInt(portfolioId);
    const puId = BigInt(body.portfolioUserId);

    // Refuse to remove the creator — they own the portfolio.
    const [target] = await db
      .select({ id: portfolioUsers.id, role: portfolioUsers.role })
      .from(portfolioUsers)
      .where(
        and(
          eq(portfolioUsers.id, puId),
          eq(portfolioUsers.portfolioId, pId),
        ),
      )
      .limit(1);
    if (!target) {
      return NextResponse.json(
        { error: "Membership not found in this portfolio" },
        { status: 404 },
      );
    }
    if (target.role === "creator") {
      return NextResponse.json(
        { error: "Cannot remove the portfolio creator" },
        { status: 400 },
      );
    }

    await db.delete(portfolioUsers).where(eq(portfolioUsers.id, puId));

    return NextResponse.json(
      { success: true, portfolioUserId: body.portfolioUserId },
      { status: 200 },
    );
  } catch (err) {
    console.error("DELETE /collaborators error:", err);
    return NextResponse.json(
      { error: "Failed to remove user" },
      { status: 500 },
    );
  }
}

// PUT: update a collaborator’s role
export async function PUT(
  req: NextRequest,
  props: { params: Promise<{ portfolioId: string }> }
) {
  const { portfolioId } = await props.params;

  const session = await getServerSession(AuthOptions);
  const denied = await denyIfNotAdmin(BigInt(portfolioId), session);
  if (denied) return denied;

  // Validate request body
  const bodySchema = z.object({
    portfolioUserId: z.string(),
    role: z.enum(ROLE_OPTIONS), // adjust to your Role union
  });

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

  try {
    // Update role for this portfolioUserId
    await db
      .update(portfolioUsers)
      .set({ role: body.role })
      .where(eq(portfolioUsers.id, BigInt(body.portfolioUserId)));

    return NextResponse.json(
      { success: true, portfolioUserId: body.portfolioUserId, role: body.role },
      { status: 200 }
    );
  } catch (err) {
    console.error("PUT /collaborators error:", err);
    return NextResponse.json(
      { error: "Failed to update role" },
      { status: 500 }
    );
  }
}

// POST: invite a user by email.
//
// Unified flow: in nearly every case we write a pending portfolio_invitations
// row, and the invitee accepts/declines explicitly via the in-app dropdown.
// The only fast-path is when the invitee is *already* a member of this
// portfolio — then it's just a role update with no email or invitation.
export async function POST(
  req: NextRequest,
  props: { params: Promise<{ portfolioId: string }> }
) {
  const { portfolioId } = await props.params;

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

  const email = normaliseEmail(body.email);

  const session = await getServerSession(AuthOptions);
  const denied = await denyIfNotAdmin(BigInt(portfolioId), session);
  if (denied) return denied;
  const inviterUserId = BigInt(session!.user!.dbId!);

  try {
    const pId = BigInt(portfolioId);

    const [portfolioRow] = await db
      .select({ name: portfolio.name })
      .from(portfolio)
      .where(eq(portfolio.id, pId))
      .limit(1);
    if (!portfolioRow) {
      return NextResponse.json(
        { error: "Portfolio not found" },
        { status: 404 },
      );
    }

    const [inviterRow] = await db
      .select({ firstName: user.firstName, email: user.email })
      .from(user)
      .where(eq(user.id, inviterUserId))
      .limit(1);
    const inviterName =
      inviterRow?.firstName ?? inviterRow?.email ?? "Someone at Domna";

    const appOrigin =
      process.env.NEXTAUTH_URL ?? `https://${req.headers.get("host")}`;

    const [existingUser] = await db
      .select({ id: user.id, firstName: user.firstName, email: user.email })
      .from(user)
      .where(eq(user.email, email))
      .limit(1);

    // Fast path: invitee is already a member of this portfolio. Just adjust
    // their role if it changed — no invitation, no email.
    if (existingUser) {
      const [existingMembership] = await db
        .select({ id: portfolioUsers.id, role: portfolioUsers.role })
        .from(portfolioUsers)
        .where(
          and(
            eq(portfolioUsers.portfolioId, pId),
            eq(portfolioUsers.userId, existingUser.id),
          ),
        )
        .limit(1);

      if (existingMembership) {
        if (existingMembership.role !== body.role) {
          await db
            .update(portfolioUsers)
            .set({ role: body.role })
            .where(eq(portfolioUsers.id, existingMembership.id));
        }
        return NextResponse.json(
          {
            user: {
              portfolioUserId: existingMembership.id.toString(),
              userId: existingUser.id.toString(),
              role: body.role,
              name: existingUser.firstName ?? null,
              email,
              kind: "member" as const,
            },
          },
          { status: 200 },
        );
      }
    }

    // Standard path (whether or not the user already exists): write a pending
    // invitation. The invitee accepts/declines via their in-app dropdown.
    const [invitation] = await db
      .insert(portfolioInvitations)
      .values({
        portfolioId: pId,
        email,
        role: body.role,
        invitedByUserId: inviterUserId,
      })
      .onConflictDoUpdate({
        target: [
          portfolioInvitations.portfolioId,
          portfolioInvitations.email,
        ],
        set: { role: body.role },
      })
      .returning({
        id: portfolioInvitations.id,
        role: portfolioInvitations.role,
      });

    try {
      await PortfolioInvitationEmail({
        identifier: email,
        portfolioName: portfolioRow.name,
        inviterName,
        linkUrl: appOrigin,
        mode: existingUser ? "existing-user" : "new-user",
      });
    } catch (mailErr) {
      console.error("PORTFOLIO_INVITATION_EMAIL_FAILURE", {
        email,
        error: mailErr instanceof Error ? mailErr.message : String(mailErr),
      });
    }

    return NextResponse.json(
      {
        user: {
          portfolioUserId: null,
          userId: null,
          invitationId: invitation.id.toString(),
          role: invitation.role,
          name: null,
          email,
          kind: "invitation" as const,
        },
      },
      { status: 200 },
    );
  } catch (err) {
    console.error("POST /collaborators error:", err);
    return NextResponse.json(
      { error: "Failed to invite user" },
      { status: 500 }
    );
  }
}
