import { rawListingSchema, type RawListing, type SourceScraper } from "../core/contracts"

const SALFA_BASE_URL = "https://salfausados.cl/autos"
const SALFA_LIST_URL = process.env.SALFA_SEMINUEVOS_URL ?? `${SALFA_BASE_URL}/seminuevos`
const SALFA_REQUEST_TIMEOUT_MS = 45_000
const SALFA_RETRIES = 3
const SALFA_PAGE_CONCURRENCY = Math.max(1, Number.parseInt(process.env.SALFA_PAGE_CONCURRENCY ?? "1", 10) || 1)
const SALFA_PAGE_DELAY_MS = Math.max(0, Number.parseInt(process.env.SALFA_PAGE_DELAY_MS ?? "450", 10) || 0)
const SALFA_REQUEST_JITTER_MS = Math.max(0, Number.parseInt(process.env.SALFA_REQUEST_JITTER_MS ?? "350", 10) || 0)

const defaultHeaders = {
  accept: "application/json, text/javascript, */*; q=0.01",
  "accept-language": "es-CL,es;q=0.9,en;q=0.8",
  referer: SALFA_LIST_URL,
  "x-requested-with": "XMLHttpRequest",
  "user-agent":
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
}

interface SalfaApiListing {
  idauto?: string | number
  NMARCA?: string
  NMODELO?: string
  version?: string
  precio?: string | number
  year?: string | number
  kms?: string | number
  fotos?: string
  combustible_nombre?: string
  combustible_nombre_ing?: string
  transmision_nombre?: string
  transmision_nombre_ing?: string
  nombre_cliente?: string
  clienteid?: string | number
  PALABRACLAVE?: string
  categoria?: number
  NCARROCERIA?: string
  idetiqueta?: number
  titulo_etiqueta?: string
  color_etiqueta?: string
  color_letra_etiqueta?: string
}

interface SalfaApiResponse {
  current_page?: number
  last_page?: number
  total?: number
  data?: SalfaApiListing[]
}

interface SalfaPageResult {
  page: number
  totalAvailable?: number
  totalPages: number
  items: RawListing[]
}

export interface SalfaScrapeOptions {
  maxPages?: number
  pageConcurrency?: number
  onProgress?: (message: string) => void
}

const normalizeText = (value?: string) => value?.replace(/\s+/g, " ").trim() ?? ""

const parseNumber = (value?: string | number | null) => {
  if (typeof value === "number") {
    return Number.isFinite(value) ? Math.trunc(value) : undefined
  }

  const digits = String(value ?? "").replace(/[^\d]/g, "")
  return digits ? Number(digits) : undefined
}

const toAbsoluteUrl = (value?: string) => {
  if (!value) {
    return undefined
  }

  return new URL(value, SALFA_BASE_URL).toString()
}

const normalizeSlug = (value?: string) =>
  normalizeText(value)
    .normalize("NFD")
    .replace(/\p{Diacritic}/gu, "")
    .toUpperCase()

const mapWithConcurrency = async <T, R>(
  values: T[],
  concurrency: number,
  mapper: (value: T, index: number) => Promise<R>
) => {
  const results = new Array<R>(values.length)
  let nextIndex = 0

  const worker = async () => {
    while (nextIndex < values.length) {
      const currentIndex = nextIndex
      nextIndex += 1
      results[currentIndex] = await mapper(values[currentIndex], currentIndex)
    }
  }

  await Promise.all(
    Array.from({ length: Math.max(1, Math.min(concurrency, values.length || 1)) }, async () => {
      await worker()
    })
  )

  return results
}

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const randomJitter = () => (SALFA_REQUEST_JITTER_MS > 0 ? Math.floor(Math.random() * (SALFA_REQUEST_JITTER_MS + 1)) : 0)

const inferRegionFromBranch = (branch?: string) => {
  const normalized = normalizeSlug(branch)

  if (!normalized) {
    return undefined
  }

  if (normalized.includes("ARICA")) return "Arica y Parinacota"
  if (normalized.includes("IQUIQUE")) return "Tarapaca"
  if (normalized.includes("CALAMA") || normalized.includes("ANTOFAGASTA")) return "Antofagasta"
  if (normalized.includes("COPIAPO")) return "Atacama"
  if (normalized.includes("LA SERENA")) return "Coquimbo"
  if (normalized.includes("SANTIAGO") || normalized.includes("HUECHURABA") || normalized.includes("MACUL")) {
    return "Metropolitana"
  }
  if (normalized.includes("CONCEPCION")) return "Biobio"
  if (normalized.includes("TEMUCO")) return "Araucania"

  return undefined
}

const inferCommuneFromBranch = (branch?: string) => {
  const normalized = normalizeSlug(branch)

  if (!normalized) {
    return undefined
  }

  if (normalized.includes("SANTIAGO MOVICENTER")) return "Huechuraba"
  if (normalized.includes("SANTIAGO AUTOPARK")) return "Macul"
  if (normalized.includes("LA SERENA")) return "La Serena"
  if (normalized.includes("CONCEPCION")) return "Concepcion"

  const compact = normalizeText(branch)
  const firstChunk = compact.split("-")[0]?.trim()
  return firstChunk || undefined
}

const extractBranchName = (value?: string) => {
  const text = normalizeText(value)
  const match = text.match(/\(([^)]+)\)/)
  return normalizeText(match?.[1] ?? text)
}

const toErrorMessage = (error: unknown) => {
  if (error instanceof DOMException && error.name === "TimeoutError") {
    return "Timeout del sitio remoto"
  }

  return error instanceof Error ? error.message : "fallo desconocido"
}

const decodeTransmission = (item: SalfaApiListing) => {
  if (item.transmision_nombre) {
    return item.transmision_nombre
  }

  if (item.transmision_nombre_ing) {
    return item.transmision_nombre_ing
  }

  return undefined
}

const decodeFuel = (item: SalfaApiListing) => {
  if (item.combustible_nombre) {
    return item.combustible_nombre
  }

  if (item.combustible_nombre_ing) {
    return item.combustible_nombre_ing
  }

  return undefined
}

export class SalfaScraper implements SourceScraper {
  source = "SALFA" as const

  async scrape(): Promise<RawListing[]> {
    return this.scrapeAll()
  }

  async scrapeAll(options: SalfaScrapeOptions = {}): Promise<RawListing[]> {
    const pageConcurrency = options.pageConcurrency ?? SALFA_PAGE_CONCURRENCY
    const firstPage = await this.fetchPage(1)

    if (firstPage.items.length === 0) {
      throw new Error("Salfa no devolvio avisos en la pagina inicial")
    }

    const totalPages = Math.max(1, Math.min(options.maxPages ?? firstPage.totalPages, firstPage.totalPages))
    const pageNumbers = Array.from({ length: Math.max(totalPages - 1, 0) }, (_, index) => index + 2)

    options.onProgress?.(
      `Pagina 1/${totalPages}: ${firstPage.items.length} avisos, total reportado ${firstPage.totalAvailable ?? "?"}`
    )

    const remainingPages = await mapWithConcurrency(pageNumbers, pageConcurrency, async (page) => {
      const result = await this.fetchPage(page)
      options.onProgress?.(`Pagina ${page}/${totalPages}: ${result.items.length} avisos`)
      return result
    })

    const seen = new Set<string>()
    const items: RawListing[] = []

    for (const item of [firstPage, ...remainingPages].flatMap((page) => page.items)) {
      if (seen.has(item.externalId)) {
        continue
      }

      seen.add(item.externalId)
      items.push(item)
    }

    return items
  }

  private async fetchPage(page: number): Promise<SalfaPageResult> {
    const url = new URL(SALFA_LIST_URL)
    url.searchParams.set("page", String(page))
    url.searchParams.set("ajax", "1")

    let lastError: Error | undefined

    for (let attempt = 1; attempt <= SALFA_RETRIES; attempt += 1) {
      try {
        if (SALFA_PAGE_DELAY_MS > 0) {
          await sleep(SALFA_PAGE_DELAY_MS + randomJitter())
        }

        const response = await fetch(url, {
          headers: defaultHeaders,
          signal: AbortSignal.timeout(SALFA_REQUEST_TIMEOUT_MS)
        })

        if (!response.ok) {
          throw new Error(
            `Listado Salfa respondio ${response.status} ${response.statusText} en page=${page} (intento ${attempt}/${SALFA_RETRIES})`
          )
        }

        const payload = (await response.json()) as SalfaApiResponse
        const data = Array.isArray(payload.data) ? payload.data : []

        return {
          page,
          totalAvailable: parseNumber(payload.total),
          totalPages: Math.max(1, parseNumber(payload.last_page) ?? 1),
          items: data.map((item) => this.mapApiItem(item)).filter((item): item is RawListing => Boolean(item))
        }
      } catch (error) {
        lastError = new Error(`Listado Salfa fallo en page=${page}: ${toErrorMessage(error)}`)
      }

      if (attempt < SALFA_RETRIES) {
        await sleep(400 * attempt)
      }
    }

    throw lastError ?? new Error(`No pude descargar Salfa en page=${page}`)
  }

  private mapApiItem(item: SalfaApiListing): RawListing | undefined {
    const externalId = normalizeText(String(item.idauto ?? ""))
    const make = normalizeText(item.NMARCA)
    const model = normalizeText(item.NMODELO)
    const version = normalizeText(item.version)
    const year = parseNumber(item.year)
    const priceClp = parseNumber(item.precio)
    const branch = extractBranchName(item.nombre_cliente)

    if (!externalId || !make || !model || !year || !priceClp) {
      return undefined
    }

    const title = [make, model, version].filter(Boolean).join(" ")

    return rawListingSchema.parse({
      source: this.source,
      externalId,
      url: `${SALFA_BASE_URL}/ficha/${externalId}`,
      title,
      priceClp,
      year,
      mileageKm: parseNumber(item.kms),
      imageUrl: toAbsoluteUrl(item.fotos),
      region: inferRegionFromBranch(branch),
      commune: inferCommuneFromBranch(branch),
      sellerName: "Salfa Usados",
      transmissionText: decodeTransmission(item),
      fuelText: decodeFuel(item),
      rawPayload: {
        listUrl: SALFA_LIST_URL,
        branch,
        branchRaw: item.nombre_cliente,
        clientId: item.clienteid,
        category: item.categoria,
        bodyStyle: item.NCARROCERIA,
        etiquetaId: item.idetiqueta,
        etiqueta: item.titulo_etiqueta,
        etiquetaColor: item.color_etiqueta,
        etiquetaTextColor: item.color_letra_etiqueta,
        keyword: item.PALABRACLAVE
      }
    })
  }
}
