import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { db } from "@/app/db/db";
import { postcodeSearch, OSPlacesItem } from "@/app/db/schema/addresses";
import { property } from "@/app/db/schema/property";
import { and, desc, eq, inArray } from "drizzle-orm";
import { lookupPostcode } from "@/app/api/postcode/LookupPostcode";
import { lookupOsPlaces } from "@/app/api/postcode/LookupOsPlaces";
import {
  cacheAgeDays,
  isCacheFresh,
  normalisePostcode,
  postcodeCacheKeys,
  toAddressCandidates,
} from "@/lib/postcodeSearch/model";

// GET ?postcode= — address candidates for the add-properties journey
// (ADR-0007): postcodes.io for validation + geography every time, OS Places
// through the 30-day postcode_search read-through cache.
export async function GET(
  request: NextRequest,
  props: { params: Promise<{ portfolioId: string }> },
) {
  const session = await getServerSession(AuthOptions);
  if (!session?.user?.email) {
    return NextResponse.json({ error: "Unauthorised" }, { status: 401 });
  }

  const { portfolioId } = await props.params;
  if (!/^\d+$/.test(portfolioId)) {
    return NextResponse.json({ error: "Invalid portfolio id" }, { status: 400 });
  }

  const postcode = normalisePostcode(
    request.nextUrl.searchParams.get("postcode") ?? "",
  );
  if (!postcode) {
    return NextResponse.json(
      { error: "That doesn't look like a full UK postcode" },
      { status: 400 },
    );
  }

  try {
    const geoLookup = await lookupPostcode(postcode);
    if (!geoLookup.result) {
      const notFound = geoLookup.status === 404;
      return NextResponse.json(
        { error: notFound ? "Postcode not found" : (geoLookup.error ?? "Postcode lookup failed") },
        { status: notFound ? 404 : 502 },
      );
    }
    const geo = {
      localAuthority: geoLookup.result.admin_district ?? null,
      constituency: geoLookup.result.parliamentary_constituency ?? null,
    };

    const now = new Date();
    // refresh=1 bypasses the cache ("Refresh from source") — the upsert below
    // still rewrites the row, so the next reader gets the fresh copy.
    const refresh = request.nextUrl.searchParams.get("refresh") === "1";
    const cached = await db
      .select()
      .from(postcodeSearch)
      .where(inArray(postcodeSearch.postcode, postcodeCacheKeys(postcode)))
      .orderBy(desc(postcodeSearch.lastUpdatedAt))
      .limit(1);

    let items: OSPlacesItem[];
    let source: "cache" | "live";
    let fetchedAt: Date;
    if (!refresh && cached.length > 0 && isCacheFresh(cached[0].lastUpdatedAt, now)) {
      items = cached[0].resultData?.results ?? [];
      source = "cache";
      fetchedAt = cached[0].lastUpdatedAt;
    } else {
      const os = await lookupOsPlaces(postcode);
      if (os.error || !os.data) {
        return NextResponse.json(
          { error: os.error ?? "Failed to fetch address data" },
          { status: os.status >= 400 ? os.status : 502 },
        );
      }
      items = os.results ?? [];
      source = "live";
      fetchedAt = now;
      await db
        .insert(postcodeSearch)
        .values({ postcode, resultData: os.data, lastUpdatedAt: now })
        .onConflictDoUpdate({
          target: postcodeSearch.postcode,
          set: { resultData: os.data, lastUpdatedAt: now },
        });
    }

    const candidates = toAddressCandidates(items, postcode);
    const uprns = candidates.map((c) => BigInt(c.uprn));
    const existing = uprns.length
      ? await db
          .select({ uprn: property.uprn })
          .from(property)
          .where(
            and(
              eq(property.portfolioId, BigInt(portfolioId)),
              inArray(property.uprn, uprns),
            ),
          )
      : [];
    const inPortfolio = new Set(existing.map((r) => r.uprn?.toString()));

    return NextResponse.json({
      postcode,
      geo,
      source,
      cacheAgeDays: cacheAgeDays(fetchedAt, now),
      candidates: candidates.map((c) => ({
        ...c,
        alreadyInPortfolio: inPortfolio.has(c.uprn),
      })),
    });
  } catch (err) {
    console.error("GET /properties/search error:", err);
    return NextResponse.json({ error: "Internal server error" }, { status: 500 });
  }
}
