import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { and, eq } from "drizzle-orm";
import { z } from "zod";

import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { db } from "@/app/db/db";
import {
  portfolioCapabilities,
  portfolioUsers,
} from "@/app/db/schema/portfolio";
import { user } from "@/app/db/schema/users";
import { applyDealPropertyUpdate } from "@/app/lib/dealPropertyUpdate";

const patchSchema = z.object({
  dealId: z.string().min(1, "dealId is required"),
  fields: z.record(z.unknown()),
});

/**
 * PATCH /api/portfolio/[portfolioId]/deal-properties
 *
 * Single update path for whitelisted, role-gated fields on
 * `hubspot_deal_data`. The route is responsible for AuthN + portfolio role
 * lookup; per-field validation, permission check, DB write and HubSpot
 * push are delegated to `applyDealPropertyUpdate`.
 *
 * Body shape:
 *   {
 *     "dealId": "12345",
 *     "fields": {
 *       "pibi_order_date":     "2025-03-12T00:00:00.000Z" | null,
 *       "pibi_completed_date": "2025-04-02T00:00:00.000Z" | null,
 *       ...
 *     }
 *   }
 *
 * Response:
 *   200 { results: { [field]: { ok: true } | { ok: false, error } },
 *         hubspotSync: "ok" | "failed" | "skipped",
 *         hubspotError?: string }
 */
export async function PATCH(
  req: NextRequest,
  props: { params: Promise<{ portfolioId: string }> },
) {
  const { portfolioId } = await props.params;

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

  let body: unknown;
  try {
    body = await req.json();
  } catch {
    return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
  }

  const parsed = patchSchema.safeParse(body);
  if (!parsed.success) {
    return NextResponse.json(
      { error: parsed.error.flatten() },
      { status: 400 },
    );
  }

  const { dealId, fields } = parsed.data;

  // Look up the calling user's role on this portfolio. The service
  // enforces per-field permissions but we still need a role string to
  // pass through.
  const userRow = await db
    .select({ id: user.id })
    .from(user)
    .where(eq(user.email, session.user.email))
    .limit(1);

  if (!userRow[0]) {
    return NextResponse.json({ error: "User not found" }, { status: 404 });
  }

  const portfolioUserRow = await db
    .select({ role: portfolioUsers.role })
    .from(portfolioUsers)
    .where(
      and(
        eq(portfolioUsers.portfolioId, BigInt(portfolioId)),
        eq(portfolioUsers.userId, userRow[0].id),
      ),
    )
    .limit(1);

  const role = portfolioUserRow[0]?.role;
  if (!role) {
    return NextResponse.json(
      { error: "No portfolio access" },
      { status: 403 },
    );
  }

  // Capabilities are orthogonal to role — used by approver-gated fields
  // (e.g. property_halted_date / _reason in issue #255).
  const capabilityRows = await db
    .select({ capability: portfolioCapabilities.capability })
    .from(portfolioCapabilities)
    .where(
      and(
        eq(portfolioCapabilities.portfolioId, BigInt(portfolioId)),
        eq(portfolioCapabilities.userId, userRow[0].id),
      ),
    );
  const capabilities = capabilityRows.map((r) => r.capability);

  try {
    const outcome = await applyDealPropertyUpdate({
      dealId,
      fields,
      role,
      capabilities,
    });

    return NextResponse.json(outcome);
  } catch (err) {
    console.error("[deal-properties PATCH]", err);
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 },
    );
  }
}
