import { OSPlacesHeader, OSPlacesResponse } from "@/app/db/schema/addresses";

const OS_API_URL = "https://api.os.uk/search/places/v1/postcode";
const OS_API_KEY = process.env.OS_API_KEY!;

type OSPlacesItem = { DPA?: Record<string, any>; LPI?: Record<string, any> };

export interface OSPlacesResult {
  status: number;
  // First page response (kept for completeness)
  data?: OSPlacesResponse;
  // All results across pages (flattened)
  results?: OSPlacesItem[];
  error?: string;
}

/**
 * Fetch a single page from OS Places
 */
async function fetchOsPlacesPage(
  postcode: string,
  offset: number,
  maxResults: number,
  retries: number,
  delay: number
): Promise<{ status: number; data?: OSPlacesResponse; error?: string }> {
  const url = `https://api.os.uk/search/places/v1/postcode?postcode=${encodeURIComponent(
    postcode
  )}&dataset=DPA,LPI&maxresults=100&offset=${offset}&key=${process.env.OS_API_KEY}`;

  try {
    const res = await fetch(url, { method: "GET" });

    if (res.ok) {
      const data: OSPlacesResponse = await res.json();
      return { status: res.status, data };
    }

    // Retry on transient issues (rate limit, server errors)
    if ([429, 500, 503].includes(res.status) && retries > 0) {
      await new Promise((r) => setTimeout(r, delay));
      return fetchOsPlacesPage(
        postcode,
        offset,
        maxResults,
        retries - 1,
        delay * 2
      );
    }

    // Map known errors
    let errorMessage = "Unexpected error";
    switch (res.status) {
      case 400:
        errorMessage = "Bad request — malformed postcode or query";
        break;
      case 401:
        errorMessage = "Unauthorized — check OS API key";
        break;
      case 403:
        errorMessage = "Forbidden — insufficient permissions for API access";
        break;
      case 404:
        errorMessage = "Postcode not found";
        break;
      case 405:
        errorMessage = "Method not allowed";
        break;
      case 429:
        errorMessage = "Rate limit exceeded — too many requests";
        break;
      case 503:
        errorMessage = "Service temporarily unavailable";
        break;
      default:
        errorMessage = `Unhandled error (${res.status})`;
    }
    return { status: res.status, error: errorMessage };
  } catch (err: any) {
    if (retries > 0) {
      await new Promise((r) => setTimeout(r, delay));
      return fetchOsPlacesPage(
        postcode,
        offset,
        maxResults,
        retries - 1,
        delay * 2
      );
    }
    return { status: 500, error: err?.message || "Network error" };
  }
}

/**
 * Calls the Ordnance Survey Places API for a given postcode,
 * with offset-based pagination, retry handling, and structured errors.
 */
export async function lookupOsPlaces(
  postcode: string,
  {
    maxResults = 100,
    retries = 2,
    delay = 1000,
  }: { maxResults?: number; retries?: number; delay?: number } = {}
): Promise<OSPlacesResult> {
  const normalized = postcode.toUpperCase().trim();

  // Page 1
  const first = await fetchOsPlacesPage(
    normalized,
    0,
    maxResults,
    retries,
    delay
  );
  if (first.error || !first.data) {
    return { status: first.status, error: first.error };
  }

  const total =
    first.data.header?.totalresults ?? first.data.results?.length ?? 0;
  let allResults: OSPlacesItem[] = first.data.results ?? [];

  // If more pages exist, fetch them in a loop
  for (let offset = maxResults; offset < total; offset += maxResults) {
    const page = await fetchOsPlacesPage(
      normalized,
      offset,
      maxResults,
      retries,
      delay
    );
    if (page.error || !page.data) {
      // Return what we have so far but indicate partial failure
      return {
        status: page.status,
        data: first.data,
        results: allResults,
        error: `Failed to fetch page at offset ${offset}: ${page.error ?? "Unknown error"}`,
      };
    }
    if (page.data.results?.length) {
      allResults = allResults.concat(page.data.results);
    }
  }

  return {
    status: 200,
    data: {
      ...first.data,
      header: {
        ...(first.data.header ?? {}),
        totalresults: allResults.length,
      },
      results: allResults,
    },
    results: allResults,
  };
}
