import { createDbService } from '@/server/services/db.js';
import { insertAddressZipCacheMiss } from '@/server/db/queries/address-zip-cache-misses.js';
import { captureCaught } from '@/server/observability/capture.server.js';

const CKAN_RESOURCE_ID = 'a7296d1a-f8c9-4b70-96c7-a54a9f1a0b4c';
const CKAN_BASE = 'https://data.gov.il/api/3/action/datastore_search';

export type ZipSource = 'r2_cache' | 'lazy_fetched' | 'manual_override' | 'unresolvable';

export interface ResolveZipResult {
  zip?: string;
  validated: boolean;
  source: ZipSource;
}

export interface ResolveZipArgs {
  cityCode: string;
  streetCode?: string;
  houseNumber?: string;
  r2: {
    get(key: string): Promise<{ text(): Promise<string> } | null>;
    put(key: string, body: string): Promise<void>;
  };
  userId?: string;
}

/**
 * Resolves a 7-digit Israeli ZIP code for a given city + street code.
 *
 * Resolution order:
 *  1. R2 cache (per-city JSON map of streetCode → ZIP)
 *  2. Lazy CKAN fetch from data.gov.il postal-codes dataset
 *  3. Unresolvable (logs telemetry miss)
 *
 * Uses `createDbService(env)` factory — caller must pass `env: { DATABASE_URL }`.
 */
export async function resolveZip(
  env: { DATABASE_URL: string },
  args: ResolveZipArgs,
): Promise<ResolveZipResult> {
  const { cityCode, streetCode, r2 } = args;
  const db = createDbService(env);

  // ── 1. R2 cache lookup ───────────────────────────────────────────────────
  if (streetCode) {
    const cacheKey = `cache/address/zips/${cityCode}.json`;
    const cached = await r2.get(cacheKey);
    if (cached) {
      const map: Record<string, string> = JSON.parse(await cached.text());
      const zip = map[streetCode];
      if (zip) return { zip, validated: true, source: 'r2_cache' };
    }
  }

  // ── 2. Lazy CKAN fetch ───────────────────────────────────────────────────
  if (streetCode) {
    try {
      const params = new URLSearchParams({
        resource_id: CKAN_RESOURCE_ID,
        q: streetCode,
        limit: '5',
      });
      const res = await fetch(`${CKAN_BASE}?${params}`, {
        signal: AbortSignal.timeout(5_000),
      });
      const json: unknown = await res.json();
      type CkanResponse = {
        result?: { records?: Array<Record<string, string>> };
      };
      const record = (json as CkanResponse)?.result?.records?.[0];
      const zip: string | undefined = record?.['מיקוד'];
      if (zip && /^\d{7}$/.test(zip)) {
        // Write-back to R2 cache
        const cacheKey = `cache/address/zips/${cityCode}.json`;
        const existing = await r2.get(cacheKey);
        const map: Record<string, string> = existing ? JSON.parse(await existing.text()) : {};
        map[streetCode] = zip;
        await r2.put(cacheKey, JSON.stringify({ ...map, lastSeededAt: new Date().toISOString() }));
        // Telemetry
        await insertAddressZipCacheMiss(db, {
          cityCode,
          streetCode,
          houseNumber: args.houseNumber,
          resolution: 'lazy_fetched',
          resolvedZip: zip,
          userId: args.userId,
        });
        return { zip, validated: true, source: 'lazy_fetched' };
      }
    } catch (err) {
      captureCaught(err, {
        scope: 'server.address.resolveZip.ckan',
        severity: 'warning',
      });
      // CKAN timeout / network error — fall through to unresolvable
    }
  }

  // ── 3. Unresolvable ──────────────────────────────────────────────────────
  await insertAddressZipCacheMiss(db, {
    cityCode,
    streetCode,
    houseNumber: args.houseNumber,
    resolution: 'unresolvable',
    userId: args.userId,
  });
  return { validated: false, source: 'unresolvable' };
}
