import { Prisma, type SourceName } from "@prisma/client"

import { normalizeListing } from "../../scraper/core/normalize"
import { BrunoFritschScraper } from "../../scraper/sources/bruno-fritsch"
import { MaritanoScraper } from "../../scraper/sources/maritano"
import { PortilloSurScraper } from "../../scraper/sources/portillo-sur"
import { SalfaScraper } from "../../scraper/sources/salfa"
import { SalazarIsraelScraper } from "../../scraper/sources/salazar-israel"
import { SergioEscobarScraper } from "../../scraper/sources/sergio-escobar"
import type { RawListing, SourceScraper } from "../../scraper/core/contracts"
import type { ScrapePreviewItem, ScrapePreviewResponse } from "../../types/scrape-preview"
import { prisma } from "./prisma"

const transmissionLabel: Record<string, string> = {
  AUTOMATIC: "Automatica",
  MANUAL: "Manual",
  CVT: "CVT",
  DCT: "DCT",
  UNKNOWN: "Por validar"
}

const fuelLabel: Record<string, string> = {
  GASOLINE: "Gasolina",
  DIESEL: "Diesel",
  HYBRID: "Hibrido",
  ELECTRIC: "Electrico",
  LPG: "GLP",
  UNKNOWN: "Por validar"
}

interface StoredNormalizedSummary {
  make: string
  model: string
  version?: string
  transmission: string
  transmissionLabel: string
  fuel: string
  fuelLabel: string
  normalizedLabel: string
  comparisonKey: string
}

interface SourceConfig {
  sourceName: SourceName
  displayName: ScrapePreviewItem["source"]
  sourceUrl: string
  createScraper: () => SourceScraper
}

const sourceConfigs = {
  maritano: {
    sourceName: "MARITANO",
    displayName: "Maritano",
    sourceUrl: "https://mye.cl/vehiculos-usados/",
    createScraper: () => new MaritanoScraper()
  },
  "bruno-fritsch": {
    sourceName: "BRUNO_FRITSCH",
    displayName: "Bruno Fritsch",
    sourceUrl: "https://www.brunofritsch.cl/autos-usados",
    createScraper: () => new BrunoFritschScraper()
  },
  salfa: {
    sourceName: "SALFA",
    displayName: "Salfa",
    sourceUrl: "https://salfausados.cl/autos/seminuevos",
    createScraper: () => new SalfaScraper()
  },
  "salazar-israel": {
    sourceName: "SALAZAR_ISRAEL",
    displayName: "Salazar Israel",
    sourceUrl: "https://www.salazarisrael.cl/vehiculos/usado",
    createScraper: () => new SalazarIsraelScraper()
  },
  "portillo-sur": {
    sourceName: "PORTILLO_SUR",
    displayName: "Portillo Sur",
    sourceUrl: "https://www.portillosur.cl/usados/concepcion/",
    createScraper: () => new PortilloSurScraper()
  },
  "sergio-escobar": {
    sourceName: "SERGIO_ESCOBAR",
    displayName: "Sergio Escobar",
    sourceUrl:
      "https://sergioescobarusados.cl/web/autos-usados?VehiculoEsSearch%5Bsegmento_filter%5D=&VehiculoEsSearch%5Bmarca_filter%5D=&VehiculoEsSearch%5Bmodelo_filter%5D=&VehiculoEsSearch%5Byear_range%5D=2014%2C2026&VehiculoEsSearch%5Bkms_range%5D=56%2C940000&VehiculoEsSearch%5Bprecio_range%5D=5000000%2C39000000&search-button=",
    createScraper: () => new SergioEscobarScraper()
  }
} as const satisfies Record<string, SourceConfig>

export type PersistedSourceSlug = keyof typeof sourceConfigs

export interface SourceIngestResult {
  slug: PersistedSourceSlug
  source: ScrapePreviewItem["source"]
  fetchedCount: number
  newCount: number
  updatedCount: number
  unavailableCount: number
  finishedAt: string
}

export interface PersistListingsResult {
  fetchedCount: number
  newCount: number
  updatedCount: number
  unavailableCount: number
  finishedAt: Date
}

const listingSelect = {
  id: true,
  externalId: true,
  sourceUrl: true,
  title: true,
  priceClp: true,
  modelYear: true,
  mileageKm: true,
  region: true,
  commune: true,
  sellerText: true,
  normalizedSummary: true,
  lastScrapedAt: true,
  images: {
    orderBy: {
      position: "asc" as const
    },
    take: 1,
    select: {
      imageUrl: true
    }
  }
} satisfies Prisma.SourceListingSelect

type StoredListingRecord = Prisma.SourceListingGetPayload<{
  select: typeof listingSelect
}>

const asJsonInput = (value: unknown) => (value === undefined ? Prisma.JsonNull : (value as Prisma.InputJsonValue))

const isPersistedSourceSlug = (value: string): value is PersistedSourceSlug => value in sourceConfigs

export const getSourceConfig = (slug: PersistedSourceSlug) => sourceConfigs[slug]

const parseStoredSummary = (value: Prisma.JsonValue | null): StoredNormalizedSummary | undefined => {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return undefined
  }

  const raw = value as Record<string, unknown>
  const make = typeof raw.make === "string" ? raw.make : ""
  const model = typeof raw.model === "string" ? raw.model : ""
  const transmissionLabelValue =
    typeof raw.transmissionLabel === "string" ? raw.transmissionLabel : transmissionLabel.UNKNOWN
  const fuelLabelValue = typeof raw.fuelLabel === "string" ? raw.fuelLabel : fuelLabel.UNKNOWN

  if (!make || !model) {
    return undefined
  }

  return {
    make,
    model,
    version: typeof raw.version === "string" ? raw.version : undefined,
    transmission: typeof raw.transmission === "string" ? raw.transmission : "UNKNOWN",
    transmissionLabel: transmissionLabelValue,
    fuel: typeof raw.fuel === "string" ? raw.fuel : "UNKNOWN",
    fuelLabel: fuelLabelValue,
    normalizedLabel: typeof raw.normalizedLabel === "string" ? raw.normalizedLabel : `${make} ${model}`,
    comparisonKey: typeof raw.comparisonKey === "string" ? raw.comparisonKey : `${make}|${model}`
  }
}

const mapStoredListingToPreviewItem = (
  config: SourceConfig,
  item: StoredListingRecord,
  fallbackIndex: number
): ScrapePreviewItem => {
  const summary = parseStoredSummary(item.normalizedSummary)

  return {
    id: item.id,
    source: config.displayName,
    externalId: item.externalId,
    title: item.title,
    sourceUrl: item.sourceUrl,
    imageUrl: item.images[0]?.imageUrl,
    sellerName: item.sellerText ?? config.displayName,
    year: item.modelYear,
    priceClp: item.priceClp,
    mileageKm: item.mileageKm ?? undefined,
    transmission: summary?.transmissionLabel ?? transmissionLabel.UNKNOWN,
    fuel: summary?.fuelLabel ?? fuelLabel.UNKNOWN,
    make: summary?.make ?? item.title.split(" ")[0] ?? "Por validar",
    model: summary?.model ?? item.title.split(" ")[1] ?? "Por validar",
    version: summary?.version,
    normalizedLabel: summary?.normalizedLabel ?? item.title,
    comparisonKey: summary?.comparisonKey ?? `${item.externalId}-${fallbackIndex}`
  }
}

const buildNormalizedSummary = (item: RawListing) => {
  const normalized = normalizeListing(item)

  return {
    make: normalized.make,
    model: normalized.model,
    version: normalized.version,
    transmission: normalized.transmission,
    transmissionLabel: transmissionLabel[normalized.transmission] ?? normalized.transmission,
    fuel: normalized.fuel,
    fuelLabel: fuelLabel[normalized.fuel] ?? normalized.fuel,
    normalizedLabel: normalized.normalizedLabel,
    comparisonKey: normalized.comparisonKey
  } satisfies StoredNormalizedSummary
}

export const persistedSourceSlugs = Object.keys(sourceConfigs) as PersistedSourceSlug[]

export function assertPersistedSourceSlug(value: string): PersistedSourceSlug {
  if (!isPersistedSourceSlug(value)) {
    throw new Error(`Fuente no soportada: ${value}`)
  }

  return value
}

export async function loadStoredPreview(sourceSlug: PersistedSourceSlug): Promise<ScrapePreviewResponse> {
  const config = getSourceConfig(sourceSlug)
  const [items, latestRun] = await Promise.all([
    prisma.sourceListing.findMany({
      where: {
        source: config.sourceName,
        isAvailable: true
      },
      select: listingSelect,
      orderBy: [{ priceClp: "asc" }, { externalId: "asc" }]
    }),
    prisma.crawlRun.findFirst({
      where: {
        source: config.sourceName
      },
      orderBy: {
        startedAt: "desc"
      }
    })
  ])

  return {
    fetchedAt:
      latestRun?.finishedAt?.toISOString() ??
      items[0]?.lastScrapedAt?.toISOString() ??
      latestRun?.startedAt.toISOString() ??
      "",
    sourceUrl: config.sourceUrl,
    totalAvailable: items.length,
    sampleSize: items.length,
    items: items.map((item, index) => mapStoredListingToPreviewItem(config, item, index))
  }
}

export async function scrapeAndStoreSource(sourceSlug: PersistedSourceSlug): Promise<SourceIngestResult> {
  const config = getSourceConfig(sourceSlug)
  const startedAt = new Date()
  const crawlRun = await prisma.crawlRun.create({
    data: {
      source: config.sourceName,
      startedAt,
      status: "SUCCESS"
    }
  })

  try {
    const scrapedItems = await config.createScraper().scrape()
    const persisted = await storeRawListingsForSource(sourceSlug, scrapedItems)

    await prisma.crawlRun.update({
      where: {
        id: crawlRun.id
      },
      data: {
        finishedAt: persisted.finishedAt,
        status: "SUCCESS",
        fetchedCount: persisted.fetchedCount,
        newCount: persisted.newCount,
        updatedCount: persisted.updatedCount,
        unavailableCount: persisted.unavailableCount,
        notes: asJsonInput({
          sourceUrl: config.sourceUrl,
          storedCount: persisted.fetchedCount - persisted.unavailableCount
        })
      }
    })

    return {
      slug: sourceSlug,
      source: config.displayName,
      fetchedCount: persisted.fetchedCount,
      newCount: persisted.newCount,
      updatedCount: persisted.updatedCount,
      unavailableCount: persisted.unavailableCount,
      finishedAt: persisted.finishedAt.toISOString()
    }
  } catch (error) {
    await prisma.crawlRun.update({
      where: {
        id: crawlRun.id
      },
      data: {
        finishedAt: new Date(),
        status: "FAILED",
        errorMessage: error instanceof Error ? error.message : "Fallo desconocido durante la persistencia"
      }
    })

    throw error
  }
}

export async function storeRawListingsForSource(
  sourceSlug: PersistedSourceSlug,
  scrapedItems: RawListing[]
): Promise<PersistListingsResult> {
  const config = getSourceConfig(sourceSlug)

  if (scrapedItems.length === 0) {
    throw new Error(`La fuente ${config.displayName} devolvio 0 resultados; no marque la base como vacia por seguridad`)
  }

  const existingItems = await prisma.sourceListing.findMany({
    where: {
      source: config.sourceName,
      externalId: {
        in: scrapedItems.map((item) => item.externalId)
      }
    },
    select: {
      id: true,
      externalId: true,
      priceClp: true
    }
  })

  const existingByExternalId = new Map(existingItems.map((item) => [item.externalId, item]))
  let newCount = 0
  let updatedCount = 0
  const scrapedAt = new Date()

  for (const item of scrapedItems) {
    const normalizedSummary = buildNormalizedSummary(item)
    const imageEntries = item.imageUrl
      ? [
          {
            imageUrl: item.imageUrl,
            position: 0
          }
        ]
      : []

    const listingData = {
      source: config.sourceName,
      externalId: item.externalId,
      sourceUrl: item.url,
      title: item.title,
      priceClp: item.priceClp,
      modelYear: item.year,
      mileageKm: item.mileageKm,
      region: item.region,
      commune: item.commune,
      sellerText: item.sellerName,
      rawPayload: asJsonInput(item.rawPayload),
      normalizedSummary: asJsonInput(normalizedSummary),
      lastSeenAt: scrapedAt,
      lastScrapedAt: scrapedAt,
      isAvailable: true
    } satisfies Prisma.SourceListingUncheckedUpdateInput

    const existing = existingByExternalId.get(item.externalId)

    await prisma.sourceListing.upsert({
      where: {
        source_externalId: {
          source: config.sourceName,
          externalId: item.externalId
        }
      },
      update: {
        ...listingData,
        images: {
          deleteMany: {},
          create: imageEntries
        }
      },
      create: {
        ...listingData,
        firstSeenAt: scrapedAt,
        images: {
          create: imageEntries
        }
      }
    })

    if (existing) {
      updatedCount += 1

      if (existing.priceClp !== item.priceClp) {
        await prisma.priceSnapshot.create({
          data: {
            sourceListingId: existing.id,
            priceClp: item.priceClp,
            observedAt: scrapedAt
          }
        })
      }

      continue
    }

    await prisma.priceSnapshot.create({
      data: {
        sourceListing: {
          connect: {
            source_externalId: {
              source: config.sourceName,
              externalId: item.externalId
            }
          }
        },
        priceClp: item.priceClp,
        observedAt: scrapedAt
      }
    })

    newCount += 1
  }

  const unavailableResult = await prisma.sourceListing.updateMany({
    where: {
      source: config.sourceName,
      externalId: {
        notIn: scrapedItems.map((item) => item.externalId)
      },
      isAvailable: true
    },
    data: {
      isAvailable: false,
      lastScrapedAt: scrapedAt
    }
  })

  return {
    fetchedCount: scrapedItems.length,
    newCount,
    updatedCount,
    unavailableCount: unavailableResult.count,
    finishedAt: scrapedAt
  }
}
