











import { createPortfolioIssueIdentityHash } from "./xombeztlyiegtybxtzkg.mjs";

const TRASTI_PORTAL_HOST = "portal.trasti.pl";
const TRASTI_API_PAGE_SIZE = 500;
export const TRASTI_API_MAX_PAGES = 40;
export const TRASTI_PORTFOLIO_COMPLETENESS_ERROR = "PORTFOLIO_COMPLETENESS_FAILED";
export const TRASTI_DETAIL_MIN_COMPLETENESS = 1;
export const TRASTI_DETAIL_MAX_ATTEMPTS = 3;
export const TRASTI_DETAIL_RETRY_DELAY_MS = 80;
export const TRASTI_DETAIL_FAILURE_FETCH = "fetch_failed";
export const TRASTI_DETAIL_FAILURE_NO_VALID_UNTIL = "no_valid_until";

function compactText(value) {
  return String(value == null ? "" : value).replace(/\s+/g, " ").trim();
}

function getTrastiSourceIdentity(item) {
  const multiOfferId = compactText(item?.multiOfferId);
  if (multiOfferId) return `multi-offer:${multiOfferId}`;
  const sourceId = compactText(item?.id);
  if (sourceId) return `id:${sourceId}`;
  const applicationNumber = compactText(item?.applicationNo)
    .replace(/[^0-9A-Za-z]/g, "")
    .toUpperCase();
  return applicationNumber ? `application:${applicationNumber}` : "";
}

function addTrastiPageSourceIdentities(items, identitiesFromEarlierPages) {
  const identitiesFromCurrentPage = new Set();
  for (const item of Array.isArray(items) ? items : []) {
    const identity = getTrastiSourceIdentity(item);
    if (!identity) continue;
    if (identitiesFromEarlierPages.has(identity)) {
      throwTrastiPaginationCompletenessError("repeated_item_across_pages");
    }
    identitiesFromCurrentPage.add(identity);
  }
  for (const identity of identitiesFromCurrentPage) {
    identitiesFromEarlierPages.add(identity);
  }
}

function throwTrastiPaginationCompletenessError(reason) {
  const error = new Error(`Import Trasti przerwany: nie udało się potwierdzić kompletności listy polis (${reason}).`);
  error.code = TRASTI_PORTFOLIO_COMPLETENESS_ERROR;
  error.reason = reason;
  throw error;
}

function getPageHostname(page) {
  try {
    return new URL(page.url()).hostname.toLowerCase();
  } catch {
    return "";
  }
}

export function isOnTrastiPortal(page) {
  return getPageHostname(page) === TRASTI_PORTAL_HOST;
}







export async function hasTrastiApiToken(page) {
  return page.evaluate(() => {
    const tokenKey = Object.keys(localStorage).find((key) => key.includes("-accesstoken-"));
    if (!tokenKey) return false;
    try {
      return Boolean(JSON.parse(localStorage.getItem(tokenKey) || "{}").secret);
    } catch {
      return false;
    }
  }).catch(() => false);
}


async function trastiApiGet(page, path, { asBase64 = false } = {}) {
  return page.evaluate(async ({ requestPath, wantBase64 }) => {
    const tokenKey = Object.keys(localStorage).find((key) => key.includes("-accesstoken-"));
    let token = "";
    try {
      token = JSON.parse(localStorage.getItem(tokenKey) || "{}").secret || "";
    } catch {
    }
    if (!token) return { ok: false, status: 401, error: "Brak tokenu MSAL w localStorage (sesja wygasła?)." };
    try {
      const response = await fetch(requestPath, {
        credentials: "include",
        headers: { Accept: "application/json, application/pdf", Authorization: `Bearer ${token}` },
      });
      if (!response.ok) return { ok: false, status: response.status };
      if (wantBase64) {
        const buffer = await response.arrayBuffer();
        const bytes = new Uint8Array(buffer);
        let binary = "";
        const chunkSize = 0x8000;
        for (let offset = 0; offset < bytes.length; offset += chunkSize) {
          binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
        }
        return { ok: true, status: response.status, base64: btoa(binary), bytes: bytes.length };
      }
      const data = await response.json().catch(() => null);
      return { ok: true, status: response.status, data };
    } catch (error) {
      return { ok: false, status: 0, error: String(error && error.message || error) };
    }
  }, { requestPath: path, wantBase64: asBase64 });
}

function buildDashboardPoliciesPath(skip, limit, issueDateFrom = "") {
  const now = new Date();
  const dateFrom = "2015-01-01T00:00:00.000Z";
  const dateTo = `${now.getFullYear() + 2}-12-31T23:59:59.999Z`;
  
  
  const issueFrom = /^\d{4}-\d{2}-\d{2}$/.test(issueDateFrom) ? `${issueDateFrom}T00:00:00.000Z` : dateFrom;
  const params = new URLSearchParams({
    registrationNo: "",
    skip: String(skip),
    limit: String(limit),
    pesel: "",
    multiOfferId: "",
    basePolicyNo: "",
    insuranceStartDateFrom: dateFrom,
    insuranceStartDateTo: dateTo,
    issueDateFrom: issueFrom,
    issueDateTo: dateTo,
    registrationNoColumnFilter: "",
    ownerColumnFilter: "",
    policySalesmanColumnFilter: "",
    policyAgentColumnFilter: "",
    multiOfferIdColumnFilter: "",
    sortColumn: "",
    sortOrder: "",
  });
  return `/api/policies/dashboardpolicies?${params.toString()}`;
}



export async function fetchTrastiPolicyItems(page, { onProgress = () => {}, issueDateFrom = "" } = {}) {
  const items = [];
  const sourceIdentities = new Set();
  let hasNext = false;
  for (let pageNumber = 0; pageNumber < TRASTI_API_MAX_PAGES; pageNumber += 1) {
    const result = await trastiApiGet(page, buildDashboardPoliciesPath(pageNumber * TRASTI_API_PAGE_SIZE, TRASTI_API_PAGE_SIZE, issueDateFrom));
    if (!result.ok) {
      console.error(`Import Trasti: API polis odpowiedziało błędem (status ${result.status || "sieciowy"}${result.error ? `: ${result.error}` : ""}).`);
      throw new Error("Nie udało się pobrać polis. Spróbuj ponownie za chwilę.");
    }
    if (
      !result.data
      || typeof result.data !== "object"
      || Array.isArray(result.data)
      || !Array.isArray(result.data.items)
      || typeof result.data.hasNext !== "boolean"
    ) {
      throwTrastiPaginationCompletenessError("invalid_page_contract");
    }
    const slice = result.data.items;
    addTrastiPageSourceIdentities(slice, sourceIdentities);
    items.push(...slice);
    onProgress({ collected: items.length, page: pageNumber + 1 });
    hasNext = result.data.hasNext;
    if (hasNext && slice.length === 0) {
      throwTrastiPaginationCompletenessError("empty_page_with_has_next");
    }
    if (!hasNext) return items;
  }
  if (hasNext) throwTrastiPaginationCompletenessError("page_limit");
  return items;
}



function createTrastiDetailFetchError(status = 0) {
  const normalizedStatus = Number(status) || 0;
  const error = new Error("Nie udało się pobrać szczegółów polisy Trasti.");
  error.code = "TRASTI_DETAIL_FETCH_FAILED";
  error.status = normalizedStatus;
  error.retryable = normalizedStatus === 0
    || normalizedStatus === 408
    || normalizedStatus === 425
    || normalizedStatus === 429
    || (normalizedStatus >= 500 && normalizedStatus <= 599);
  return error;
}

export async function fetchTrastiPolicyDetails(page, multiOfferId) {
  const normalizedId = compactText(multiOfferId).replace(/[^0-9A-Za-z]/g, "");
  if (!normalizedId) {
    const error = createTrastiDetailFetchError(400);
    error.retryable = false;
    throw error;
  }
  const result = await trastiApiGet(page, `/api/policies/${encodeURIComponent(normalizedId)}/details`);
  if (!result.ok) throw createTrastiDetailFetchError(result.status);
  if (!result.data || typeof result.data !== "object" || Array.isArray(result.data)) {
    
    
    throw createTrastiDetailFetchError(0);
  }
  return result.data;
}

function getConfirmedTrastiValidUntil(details) {
  const rawValidUntil = compactText(details?.carData?.insurance?.insuranceTime?.to);
  const match = /^(\d{4})-(\d{2})-(\d{2})(?:$|[ T])/.exec(rawValidUntil);
  if (!match) return "";
  const validUntil = `${match[1]}-${match[2]}-${match[3]}`;
  const year = Number(match[1]);
  const month = Number(match[2]);
  const day = Number(match[3]);
  const parsed = new Date(Date.UTC(year, month - 1, day));
  return parsed.getUTCFullYear() === year
    && parsed.getUTCMonth() === month - 1
    && parsed.getUTCDate() === day
    ? validUntil
    : "";
}

function throwTrastiCompletenessError({
  detailsRequested,
  detailFailures,
  detailFetchFailures,
  detailNoValidUntil,
  completeness,
}) {
  const reasons = [];
  if (detailFetchFailures > 0) reasons.push(`nieudane pobranie: ${detailFetchFailures}`);
  if (detailNoValidUntil > 0) {
    reasons.push(`brak potwierdzonej daty końca: ${detailNoValidUntil}`);
  }
  const error = new Error(
    "Nie można potwierdzić kompletności szczegółów polis Trasti. "
    + `Import został przerwany${reasons.length ? ` (${reasons.join(", ")})` : ""}.`,
  );
  error.code = TRASTI_PORTFOLIO_COMPLETENESS_ERROR;
  error.reason = "policy_details_incomplete";
  error.detailsRequested = detailsRequested;
  error.detailFailures = detailFailures;
  error.detailFetchFailures = detailFetchFailures;
  error.detailNoValidUntil = detailNoValidUntil;
  error.failureReasons = {
    [TRASTI_DETAIL_FAILURE_FETCH]: detailFetchFailures,
    [TRASTI_DETAIL_FAILURE_NO_VALID_UNTIL]: detailNoValidUntil,
  };
  error.completeness = completeness;
  throw error;
}

export function assertTrastiDetailCompleteness({
  detailsRequested,
  detailFailures,
  detailFetchFailures = 0,
  detailNoValidUntil = 0,
}) {
  const requested = Math.max(0, Math.trunc(Number(detailsRequested) || 0));
  const fetchFailures = Math.min(
    requested,
    Math.max(0, Math.trunc(Number(detailFetchFailures) || 0)),
  );
  const noValidUntil = Math.min(
    requested - fetchFailures,
    Math.max(0, Math.trunc(Number(detailNoValidUntil) || 0)),
  );
  const failures = Math.min(
    requested,
    Math.max(
      Math.max(0, Math.trunc(Number(detailFailures) || 0)),
      fetchFailures + noValidUntil,
    ),
  );
  
  
  const threshold = TRASTI_DETAIL_MIN_COMPLETENESS;
  const completeness = requested === 0 ? 1 : (requested - failures) / requested;
  const result = {
    detailsRequested: requested,
    detailsSucceeded: requested - failures,
    detailFailures: failures,
    detailFetchFailures: fetchFailures,
    detailNoValidUntil: noValidUntil,
    completeness,
    minCompleteness: threshold,
  };
  if (completeness < threshold) throwTrastiCompletenessError(result);
  return result;
}



export async function fetchTrastiPolicyDetailsBatch(page, items, {
  fetchDetails = fetchTrastiPolicyDetails,
  delayMs = 120,
  retryDelayMs = TRASTI_DETAIL_RETRY_DELAY_MS,
  wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
  onProgress = () => {},
  allowPartialForReview = false,
} = {}) {
  const targets = Array.isArray(items) ? items : [];
  const detailsById = new Map();
  let detailFailures = 0;
  let detailFetchFailures = 0;
  let detailNoValidUntil = 0;
  let detailAttempts = 0;

  for (let index = 0; index < targets.length; index += 1) {
    const policyNumber = compactText(targets[index]?.multiOfferId);
    const normalizedId = policyNumber.replace(/[^0-9A-Za-z]/g, "");
    let details = null;
    let failureReason = "";
    if (!normalizedId) {
      failureReason = TRASTI_DETAIL_FAILURE_FETCH;
    } else {
      for (let attempt = 1; attempt <= TRASTI_DETAIL_MAX_ATTEMPTS; attempt += 1) {
        detailAttempts += 1;
        let fetchError = null;
        details = null;
        try {
          details = await fetchDetails(page, policyNumber);
        } catch (error) {
          fetchError = error;
        }
        if (details && typeof details === "object" && !Array.isArray(details)) {
          if (getConfirmedTrastiValidUntil(details)) break;
          
          
          failureReason = TRASTI_DETAIL_FAILURE_NO_VALID_UNTIL;
          details = null;
          break;
        }
        const retryable = fetchError?.retryable === true;
        if (!retryable || attempt === TRASTI_DETAIL_MAX_ATTEMPTS) {
          failureReason = TRASTI_DETAIL_FAILURE_FETCH;
          break;
        }
        const backoffMs = Math.min(
          5_000,
          Math.max(0, Number(retryDelayMs) || 0) * attempt,
        );
        if (backoffMs > 0) await wait(backoffMs);
      }
    }
    if (details) {
      detailsById.set(policyNumber, details);
      detailsById.set(normalizedId, details);
    } else {
      detailFailures += 1;
      if (failureReason === TRASTI_DETAIL_FAILURE_NO_VALID_UNTIL) {
        detailNoValidUntil += 1;
      } else {
        detailFetchFailures += 1;
      }
    }

    const detailsProcessed = index + 1;
    onProgress({
      detailsProcessed,
      detailsRequested: targets.length,
      detailsSucceeded: detailsProcessed - detailFailures,
      detailFailures,
      detailFetchFailures,
      detailNoValidUntil,
      detailAttempts,
      failureReason,
    });
    const waitMs = Math.max(0, Number(delayMs) || 0);
    if (waitMs > 0 && detailsProcessed < targets.length) {
      await wait(waitMs);
    }
  }

  if (allowPartialForReview) {
    const detailsRequested = targets.length;
    const completeness = detailsRequested === 0
      ? 1
      : (detailsRequested - detailFailures) / detailsRequested;
    return {
      detailsById,
      detailAttempts,
      detailsRequested,
      detailsSucceeded: detailsRequested - detailFailures,
      detailFailures,
      detailFetchFailures,
      detailNoValidUntil,
      completeness,
      minCompleteness: TRASTI_DETAIL_MIN_COMPLETENESS,
    };
  }
  try {
    const completeness = assertTrastiDetailCompleteness({
      detailsRequested: targets.length,
      detailFailures,
      detailFetchFailures,
      detailNoValidUntil,
    });
    return { detailsById, detailAttempts, ...completeness };
  } catch (error) {
    if (error?.code === TRASTI_PORTFOLIO_COMPLETENESS_ERROR) {
      error.detailAttempts = detailAttempts;
    }
    throw error;
  }
}


export async function downloadTrastiPolicyPdfBase64(page, multiOfferId) {
  const normalizedId = compactText(multiOfferId).replace(/[^0-9A-Za-z]/g, "");
  if (!normalizedId) return null;
  const result = await trastiApiGet(page, `/api/salesApi/documentoffer?multiOfferId=${encodeURIComponent(normalizedId)}`, { asBase64: true });
  if (!result.ok || !result.base64 || Number(result.bytes) < 1000) return null;
  return { base64: result.base64, bytes: result.bytes };
}

const TRASTI_STATUS_LABELS = {
  ACTIVE: "Aktywna",
  EXPIRED: "Zakończona",
  CANCELED: "Anulowana",
  CANCELLED: "Anulowana",
};

function normalizeTrastiRegistration(value) {
  return compactText(value).replace(/[^A-Za-z0-9]/g, "").toUpperCase();
}

function normalizeTrastiDate(value) {
  
  return compactText(value).slice(0, 10);
}

function formatTrastiPremium(amount) {
  const value = Number(amount);
  if (!Number.isFinite(value) || value <= 0) return "";
  const fixed = value.toFixed(2);
  return fixed.endsWith(".00") ? fixed.slice(0, -3) : fixed.replace(".", ",");
}


function resolveTrastiIdentity(item, details) {
  const pesel = compactText(item?.ownerPesel || details?.carData?.policyHolder?.pesel).replace(/\D/g, "");
  if (/^\d{11}$/.test(pesel)) return pesel;
  const nip = compactText(item?.ownerNip).replace(/\D/g, "");
  if (/^\d{10}$/.test(nip)) return nip;
  const regon = compactText(item?.ownerRegon || details?.carData?.policyHolder?.regon).replace(/\D/g, "");
  if (/^\d{9}$/.test(regon) || /^\d{14}$/.test(regon)) return regon;
  return pesel || nip || regon;
}

function resolveTrastiClientName(item) {
  const company = compactText(item?.ownerCompanyName);
  if (company) return company;
  return [compactText(item?.ownerFirstName), compactText(item?.ownerLastName)].filter(Boolean).join(" ");
}


function buildTrastiRiskSummary(details) {
  const products = Array.isArray(details?.products) ? details.products : [];
  return products
    .filter((product) => product?.selected !== false && compactText(product?.code))
    .map((product) => {
      const code = compactText(product.code).toUpperCase();
      const premium = formatTrastiPremium(product?.premium);
      return premium ? `${code} (${premium} zł)` : code;
    })
    .join(", ")
    .slice(0, 400);
}




export function mapTrastiPolicyItems(items, detailsById = new Map(), {
  minEndDate = "",
  requireConfirmedValidUntil = false,
  mappingCollector = null,
} = {}) {
  const records = [];
  const seen = new Set();
  for (const item of Array.isArray(items) ? items : []) {
    const multiOfferId = compactText(item?.multiOfferId);
    const policyNumber = multiOfferId || compactText(item?.applicationNo);
    const clientName = resolveTrastiClientName(item);
    const status = compactText(item?.status).toUpperCase();
    if (status === "DRAFT") {
      mappingCollector?.recordFiltered("DRAFT_OFFER");
      continue;
    }
    if (/^(?:CANCELED|CANCELLED|ANULOWANA|STORNO)$/.test(status)) {
      mappingCollector?.recordFiltered("CANCELLED_POLICY");
      continue;
    }
    const normalizedPolicyNumber = policyNumber.replace(/[^0-9A-Za-z]/g, "").toLowerCase();
    const details = detailsById.get(policyNumber) || detailsById.get(normalizedPolicyNumber) || null;
    const insuranceTime = details?.carData?.insurance?.insuranceTime;
    const validFrom = normalizeTrastiDate(insuranceTime?.from) || normalizeTrastiDate(item?.insuranceStartDate);
    const validUntil = getConfirmedTrastiValidUntil(details);
    const createIssueHash = () => {
      try {
        return createPortfolioIssueIdentityHash({
          insurer: "Trasti",
          sourceId: multiOfferId || compactText(item?.id),
          policyNumber,
          validFrom,
          validUntil,
        });
      } catch {
        return "";
      }
    };
    if (!normalizedPolicyNumber || !clientName) {
      mappingCollector?.recordRejected("MISSING_REQUIRED_FIELDS", {
        identityHash: createIssueHash(),
      });
      continue;
    }
    if (requireConfirmedValidUntil && !validUntil) {
      if (mappingCollector) {
        mappingCollector.recordRejected("MISSING_CONFIRMED_END_DATE", {
          identityHash: createIssueHash(),
        });
        continue;
      }
      throwTrastiCompletenessError({
        detailsRequested: 1,
        detailFailures: 1,
        detailFetchFailures: 0,
        detailNoValidUntil: 1,
        completeness: 0,
      });
    }
    if (minEndDate && !validUntil) {
      mappingCollector?.recordRejected("MISSING_CONFIRMED_END_DATE", {
        identityHash: createIssueHash(),
      });
      continue;
    }
    if (minEndDate && validUntil < minEndDate) {
      mappingCollector?.recordFiltered("OUTSIDE_DATE_RANGE");
      continue;
    }
    const key = `${normalizedPolicyNumber}\0${validFrom}\0${validUntil}`;
    if (seen.has(key)) {
      mappingCollector?.recordDuplicate();
      continue;
    }
    seen.add(key);

    const carInfo = details?.carData?.carInfo || null;
    const registrationPlate = normalizeTrastiRegistration(item?.registrationNo || carInfo?.registrationId);
    const policyHolder = details?.carData?.policyHolder || null;
    const phone = compactText(policyHolder?.phone);
    const email = compactText(policyHolder?.email || item?.ownerEmail);
    const contactParts = [];
    if (phone) contactParts.push(`Telefon: ${phone}`);
    if (email) contactParts.push(`E-mail: ${email}`);

    records.push({
      insurer: "Trasti",
      policyNumber,
      
      
      multiOfferId: multiOfferId.slice(0, 200),
      clientName: clientName.slice(0, 160),
      identityNumber: resolveTrastiIdentity(item, details),
      contact: contactParts.join("\n"),
      registration: registrationPlate.length >= 4 && registrationPlate.length <= 10 ? registrationPlate : "Auto",
      vehicleMake: compactText(carInfo?.make).slice(0, 80),
      vehicleModel: compactText(carInfo?.model).slice(0, 120),
      vin: compactText(carInfo?.vinNumber).replace(/[^A-Za-z0-9]/g, "").toUpperCase().slice(0, 17),
      validFrom,
      validUntil,
      issueDate: normalizeTrastiDate(item?.issueDate ?? item?.createdAt ?? item?.creationDate),
      premium: formatTrastiPremium(item?.premium),
      status: TRASTI_STATUS_LABELS[status] || compactText(item?.status).slice(0, 80),
      product: "Komunikacja",
      riskSummary: buildTrastiRiskSummary(details),
    });
    mappingCollector?.recordMapped();
  }
  return records;
}
