import { config } from '../config.js';
import { query } from '../db.js';
import { listNotices, getNoticeDetail, sleep } from '../seap-client.js';
import { generateDateWindows, monthsAgo } from '../utils/date-ranges.js';
import { ensureAuthority, ensureSupplier } from './entities.js';
import { matchLocality } from '../matching/locality-matcher.js';

export async function scrapePublicNotices(startDate?: Date): Promise<number> {
  const syncState = await query(
    "SELECT last_date FROM seap.sync_state WHERE source = 'notices'"
  );

  const from = startDate
    || (syncState.rows[0]?.last_date ? new Date(syncState.rows[0].last_date) : monthsAgo(config.scraper.backfillMonths));
  const to = new Date();

  console.log(`[Notices] Scraping from ${from.toISOString()} to ${to.toISOString()}`);

  const windows = generateDateWindows(from, to, config.scraper.noticeWindowDays);
  let totalInserted = 0;

  for (const window of windows) {
    console.log(`[Notices] Window: ${window.start} → ${window.end}`);

    let pageIndex = 0;
    let hasMore = true;

    while (hasMore) {
      const result = await listNotices({
        pageIndex,
        pageSize: config.scraper.pageSize,
        startPublicationDate: window.start,
        endPublicationDate: window.end,
      });

      if (result.searchTooLong) {
        console.warn(`[Notices] searchTooLong in window ${window.start}–${window.end}`);
      }

      console.log(`[Notices] Page ${pageIndex}: ${result.items.length} items (total: ${result.total})`);

      let batchCount = 0;
      for (const item of result.items) {
        const exists = await query(
          'SELECT id FROM seap.public_notices WHERE id = $1',
          [item.caNoticeId]
        );
        if (exists.rows.length > 0) continue;

        // Fetch detail for address + contracts
        let detail;
        try {
          detail = await getNoticeDetail(item.caNoticeId);
        } catch (err: any) {
          console.warn(`[Notices] Failed detail for ${item.caNoticeId}: ${err.message}`);
          continue;
        }

        // Extract authority address from notice detail
        const caAddress = detail?.caNoticeEdit_New?.section1_New?.section1_1?.caAddress;
        const authorityCity = caAddress?.city || null;
        const authorityCounty = typeof caAddress?.county === 'object'
          ? caAddress.county.text
          : caAddress?.county || null;
        const authorityEntityId = caAddress?.entityId || null;

        // Ensure authority entity
        if (authorityEntityId) {
          await ensureAuthority(authorityEntityId);
        }

        // Match authority location to SIRUTA
        const authoritySiruta = await matchLocality(authorityCity, authorityCounty);

        // Parse CPV
        const cpvParts = item.cpvCode?.split(' - ') || [];
        const cpvCode = cpvParts[0] || null;
        const cpvName = cpvParts.slice(1).join(' - ') || null;

        // Extract contract value
        const section2 = detail?.caNoticeEdit_New?.section2_New?.section2_1_New;
        const estimatedValue = section2?.totalAcquisitionValue || null;
        const hasLots = section2?.hasLots ?? false;

        await query(
          `INSERT INTO seap.public_notices
             (id, notice_no, contract_title, cpv_code, cpv_name,
              estimated_value, contract_value, publication_date, state_date,
              procedure_type_id, procedure_type_text,
              contract_type_id, contract_type_text,
              notice_type_id, state_id, state_text,
              authority_id, authority_city, authority_county, authority_siruta, has_lots)
           VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21)
           ON CONFLICT (id) DO NOTHING`,
          [
            item.caNoticeId,
            item.noticeNo,
            item.contractTitle,
            cpvCode,
            cpvName,
            estimatedValue,
            item.ronContractValue,
            item.noticeStateDate,
            item.noticeStateDate,
            item.sysProcedureType?.id ?? null,
            item.sysProcedureType?.text ?? null,
            item.sysAcquisitionContractType?.id ?? null,
            item.sysAcquisitionContractType?.text ?? null,
            3, // CAN
            item.sysNoticeState?.id ?? null,
            item.sysNoticeState?.text ?? null,
            authorityEntityId,
            authorityCity,
            authorityCounty,
            authoritySiruta,
            hasLots,
          ]
        );

        // Extract contracts/winners from section 5
        await extractNoticeContracts(item.caNoticeId, detail);

        totalInserted++;
        batchCount++;

        if (batchCount >= config.seap.rateLimit.batchSize) {
          console.log(`[Notices] Batch pause (${config.seap.rateLimit.batchDelayMs}ms)...`);
          await sleep(config.seap.rateLimit.batchDelayMs);
          batchCount = 0;
        }
      }

      hasMore = (pageIndex + 1) * config.scraper.pageSize < result.total && !result.searchTooLong;
      pageIndex++;
    }

    await query(
      `UPDATE seap.sync_state SET last_date = $1, status = 'running', updated_at = now() WHERE source = 'notices'`,
      [window.end]
    );
  }

  await query(
    `UPDATE seap.sync_state SET status = 'completed', updated_at = now() WHERE source = 'notices'`
  );

  console.log(`[Notices] Done. Inserted ${totalInserted} new notices.`);
  return totalInserted;
}

async function extractNoticeContracts(noticeId: number, detail: any): Promise<void> {
  // Section 5 has contract awards
  const section5 = detail?.caNoticeEdit_New?.section5;
  if (!section5) return;

  const lotList = section5.contractLotList || section5.contractsArray || [];
  if (!Array.isArray(lotList)) return;

  for (const lot of lotList) {
    const winner = lot.winner || lot.winners?.[0];
    if (!winner) continue;

    const winnerCity = winner.address?.city || winner.city || null;
    const winnerCounty = typeof winner.address?.county === 'object'
      ? winner.address.county.text
      : winner.address?.county || winner.county || null;
    const winnerFiscal = winner.fiscalNumber || winner.nationalIDNumber || null;
    const winnerName = winner.officialName || winner.entityName || null;

    // Ensure winner entity if we have an entityId
    const winnerEntityId = winner.entityId || null;
    if (winnerEntityId) {
      await ensureSupplier(winnerEntityId);
    }

    const winnerSiruta = await matchLocality(winnerCity, winnerCounty);

    await query(
      `INSERT INTO seap.notice_contracts
         (notice_id, lot_number, lot_title, contract_value, currency,
          contract_date, winner_id, winner_name, winner_fiscal,
          winner_city, winner_county, winner_siruta, num_offers)
       VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)`,
      [
        noticeId,
        lot.lotNumber ?? lot.contractNo ?? null,
        lot.lotTitle ?? lot.contractTitle ?? null,
        lot.contractValue ?? lot.value ?? null,
        lot.currency || 'RON',
        lot.contractDate || null,
        winnerEntityId,
        winnerName,
        winnerFiscal,
        winnerCity,
        winnerCounty,
        winnerSiruta,
        lot.numberOfOffers ?? lot.nbTendersReceived ?? null,
      ]
    );
  }
}
