






const BAZOWY_ADRES = "https://moj.gov.pl";
const NAZWA_APLIKACJI = "HistoriaPojazdu";
const WERSJA_API = "1.1.0";
const ADRES_SESJI = `${BAZOWY_ADRES}/uslugi/engine/ng/index?xFormsAppName=${NAZWA_APLIKACJI}`;
const ADRES_API = `${BAZOWY_ADRES}/nforms/api/${NAZWA_APLIKACJI}/${WERSJA_API}`;
const LIMIT_CZASU_MS = 20000;
const PRZEGLADARKOWY_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";

export class HistoriaPojazduError extends Error {
  constructor(message, code) {
    super(message);
    this.name = "HistoriaPojazduError";
    this.code = code;
  }
}

function bladNieZnaleziono() {
  return new HistoriaPojazduError(
    "W bazie CEPiK nie ma pojazdu o podanych danych. Sprawdź numer rejestracyjny, VIN i datę pierwszej rejestracji.",
    "POJAZD_NIE_ZNALEZIONY",
  );
}

function bladLimituZapytan() {
  return new HistoriaPojazduError(
    "Serwis HistoriaPojazdu.gov chwilowo ogranicza liczbę zapytań. Odczekaj minutę i spróbuj ponownie.",
    "LIMIT_ZAPYTAN",
  );
}

function bladPolaczenia() {
  return new HistoriaPojazduError(
    "Nie udało się połączyć z serwisem HistoriaPojazdu.gov. Spróbuj ponownie za chwilę.",
    "SERWIS_NIEDOSTEPNY",
  );
}




export function walidujZapytanieHistorii(payload) {
  const registration = String(payload?.registration ?? "").replace(/[^0-9a-z]/gi, "").toLocaleUpperCase("pl").slice(0, 10);
  const vin = String(payload?.vin ?? "").replace(/[^0-9a-z]/gi, "").toLocaleUpperCase("pl").slice(0, 17);
  const firstRegistrationDate = String(payload?.firstRegistrationDate ?? "").trim().slice(0, 10);
  const pesel = String(payload?.pesel ?? "").replace(/[^0-9]/g, "").slice(0, 11);
  if (registration.length < 4) throw new Error("Wpisz pełny numer rejestracyjny pojazdu.");
  if (vin.length < 5) throw new Error("Wpisz pełny numer VIN pojazdu.");
  if (!/^\d{4}-\d{2}-\d{2}$/.test(firstRegistrationDate)) throw new Error("Wpisz datę pierwszej rejestracji pojazdu.");
  return {
    registration,
    vin,
    firstRegistrationDate,
    pesel: /^\d{11}$/.test(pesel) ? pesel : "",
  };
}



export function utworzSloikCiasteczek() {
  const ciasteczka = new Map();
  return {
    zapisz(naglowkiSetCookie) {
      for (const wpis of naglowkiSetCookie ?? []) {
        const para = String(wpis).split(";", 1)[0];
        const separator = para.indexOf("=");
        if (separator <= 0) continue;
        const nazwa = para.slice(0, separator).trim();
        const wartosc = para.slice(separator + 1).trim();
        if (nazwa) ciasteczka.set(nazwa, wartosc);
      }
    },
    naglowek() {
      return [...ciasteczka.entries()].map(([nazwa, wartosc]) => `${nazwa}=${wartosc}`).join("; ");
    },
    wartosc(nazwa) {
      return ciasteczka.get(nazwa) ?? "";
    },
  };
}

function odczytajOdczytLicznika(tekst) {
  const dopasowanie = String(tekst ?? "").match(/([0-9][0-9\s .]*)\s*([a-z]{0,4})/i);
  if (!dopasowanie) return null;
  const wartosc = Number(dopasowanie[1].replace(/[^0-9]/g, ""));
  if (!Number.isFinite(wartosc) || wartosc <= 0) return null;
  return { value: wartosc, unit: (dopasowanie[2] || "km").toLocaleLowerCase("pl") };
}

function wybierzOdczytZPodstawowych(daneBazowe) {
  const odczyty = Array.isArray(daneBazowe?.odometerReadings) ? daneBazowe.odometerReadings : [];
  const poprawne = odczyty
    .filter((odczyt) => odczyt && odczyt.error !== true && odczyt.rolledBack !== true)
    .map((odczyt) => ({ value: Number(odczyt.value), unit: String(odczyt.unit || "km").toLocaleLowerCase("pl") }))
    .filter((odczyt) => Number.isFinite(odczyt.value) && odczyt.value > 0);
  if (poprawne.length === 0) return null;
  return poprawne.reduce((najwyzszy, odczyt) => (odczyt.value > najwyzszy.value ? odczyt : najwyzszy));
}

function wybierzOdczytZOsiCzasu(daneOsiCzasu) {
  const zdarzenia = Array.isArray(daneOsiCzasu?.events) ? daneOsiCzasu.events : [];
  const odczyty = [];
  for (const zdarzenie of zdarzenia) {
    const szczegoly = Array.isArray(zdarzenie?.eventDetails) ? zdarzenie.eventDetails : [];
    for (const szczegol of szczegoly) {
      if (!/stan licznika|drogomierz/i.test(String(szczegol?.name ?? ""))) continue;
      const odczyt = odczytajOdczytLicznika(szczegol?.value);
      if (!odczyt) continue;
      const data = /^\d{4}-\d{2}-\d{2}/.test(String(zdarzenie?.eventDate ?? ""))
        ? String(zdarzenie.eventDate).slice(0, 10)
        : "";
      odczyty.push({ ...odczyt, date: data });
    }
  }
  if (odczyty.length === 0) return null;
  return odczyty.reduce((najnowszy, odczyt) => (String(odczyt.date) > String(najnowszy.date) ? odczyt : najnowszy));
}





export function zmapujDanePojazdu(daneTechniczne, daneOsiCzasu = null) {
  const techniczne = daneTechniczne?.technicalData ?? {};
  const bazowe = techniczne.basicData ?? {};
  const szczegolowe = techniczne.detailedData ?? {};
  const tekst = (wartosc) => String(wartosc ?? "").trim();
  const liczba = (wartosc) => (Number.isFinite(Number(wartosc)) && Number(wartosc) > 0 ? Number(wartosc) : null);

  const odczytBazowy = wybierzOdczytZPodstawowych(bazowe);
  const odczytZOsi = wybierzOdczytZOsiCzasu(daneOsiCzasu?.timelineData ?? daneOsiCzasu);
  let przebieg = null;
  if (odczytBazowy && odczytZOsi && odczytZOsi.value === odczytBazowy.value) {
    przebieg = { ...odczytBazowy, date: odczytZOsi.date };
  } else if (odczytBazowy) {
    przebieg = { ...odczytBazowy, date: "" };
  } else if (odczytZOsi) {
    przebieg = odczytZOsi;
  }

  return {
    vehicleType: tekst(bazowe.type),
    vehicleSubtype: tekst(bazowe.subtype),
    make: tekst(bazowe.make),
    model: tekst(bazowe.model),
    yearOfManufacture: liczba(bazowe.yearOfManufacture),
    mileage: przebieg,
    engineCapacity: liczba(szczegolowe.engineCapacity),
    enginePower: liczba(szczegolowe.maxNetEnginePower),
    fuelType: tekst(szczegolowe.fuelType),
    seats: liczba(szczegolowe.totalNumberOfSeats) ?? liczba(szczegolowe.numberOfSeats),
    grossWeight: liczba(szczegolowe.permissibleGrossWeight),
    registrationStatus: tekst(bazowe.registrationStatus),
    technicalInspectionStatus: tekst(bazowe.technicalInspectionStatus),
    hasCurrentOCPolicy: bazowe.hasCurrentOCPolicy === true,
    vehicleLost: bazowe.vehicleLost === true,
  };
}




export function sklasyfikujOdpowiedzBledu(status, cialo) {
  if (status === 429) return bladLimituZapytan();
  const kodWalidacji = String(cialo?.VALIDATION_ERROR_CODE ?? "");
  if (status === 404 && (kodWalidacji.startsWith("HIPO") || cialo?.VALIDATION_ERROR_MSG)) {
    return bladNieZnaleziono();
  }
  return bladPolaczenia();
}

async function wyslij(url, opcje = {}) {
  try {
    return await fetch(url, {
      cache: "no-store",
      redirect: "follow",
      ...opcje,
      headers: {
        "User-Agent": PRZEGLADARKOWY_USER_AGENT,
        "Accept-Language": "pl-PL,pl;q=0.9",
        ...opcje.headers,
      },
      signal: AbortSignal.timeout(LIMIT_CZASU_MS),
    });
  } catch (error) {
    if (error instanceof HistoriaPojazduError) throw error;
    throw bladPolaczenia();
  }
}

async function odczytajJson(response) {
  try {
    return await response.json();
  } catch {
    return null;
  }
}



export async function pobierzDaneZHistoriiPojazdu({ registration, vin, firstRegistrationDate }) {
  const sloik = utworzSloikCiasteczek();

  const odpowiedzSesji = await wyslij(ADRES_SESJI);
  sloik.zapisz(odpowiedzSesji.headers.getSetCookie());
  if (!odpowiedzSesji.ok) throw bladPolaczenia();

  const nfWid = `${NAZWA_APLIKACJI}:${Date.now()}`;
  const odpowiedzLogowania = await wyslij(ADRES_SESJI, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Cookie: sloik.naglowek(),
    },
    body: new URLSearchParams({
      NF_WID: nfWid,
      varKey: "NF_WID",
      varApplicationName: NAZWA_APLIKACJI,
    }).toString(),
  });
  sloik.zapisz(odpowiedzLogowania.headers.getSetCookie());
  if (!odpowiedzLogowania.ok || !sloik.wartosc("XSRF-TOKEN")) throw bladPolaczenia();

  const zapytajApi = async (koncowka) => wyslij(`${ADRES_API}/data/${koncowka}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
      Cookie: sloik.naglowek(),
      "X-Xsrf-Token": sloik.wartosc("XSRF-TOKEN"),
      Nf_wid: nfWid,
    },
    body: JSON.stringify({
      registrationNumber: registration,
      VINNumber: vin,
      firstRegistrationDate,
    }),
  });

  try {
    const odpowiedzPojazdu = await zapytajApi("vehicle-data");
    const cialoPojazdu = await odczytajJson(odpowiedzPojazdu);
    if (!odpowiedzPojazdu.ok || !cialoPojazdu?.technicalData) {
      throw sklasyfikujOdpowiedzBledu(odpowiedzPojazdu.status, cialoPojazdu);
    }

    
    
    let cialoOsiCzasu = null;
    try {
      const odpowiedzOsiCzasu = await zapytajApi("timeline-data");
      if (odpowiedzOsiCzasu.ok) cialoOsiCzasu = await odczytajJson(odpowiedzOsiCzasu);
    } catch {
      cialoOsiCzasu = null;
    }

    return zmapujDanePojazdu(cialoPojazdu, cialoOsiCzasu);
  } finally {
    
    
    await wyslij(`${ADRES_API}/close`, {
      headers: { Cookie: sloik.naglowek(), Nf_wid: nfWid, Accept: "application/json, text/plain, */*" },
    }).catch(() => {});
  }
}
