import { readFile } from "node:fs/promises"
import { resolve } from "node:path"

import { rawListingSchema, type RawListing } from "../scraper/core/contracts"
import { prisma } from "../server/utils/prisma"
import {
  assertPersistedSourceSlug,
  getSourceConfig,
  type PersistedSourceSlug,
  storeRawListingsForSource
} from "../server/utils/scrape-storage"

process.loadEnvFile?.()

interface ImportArgs {
  sourceSlug: PersistedSourceSlug
  filePath: string
}

const parseArgs = (): ImportArgs => {
  const source = process.argv[2]
  const filePath = process.argv[3]

  if (!source || !filePath) {
    console.error("Uso: npm run import:source -- <maritano|bruno-fritsch|salfa|salazar-israel|portillo-sur|sergio-escobar> <ruta-json>")
    process.exit(1)
  }

  return {
    sourceSlug: assertPersistedSourceSlug(source),
    filePath: resolve(filePath)
  }
}

const parseJsonLoose = (value: string): unknown => {
  try {
    return JSON.parse(value)
  } catch {
    const arrayStart = value.indexOf("[")
    const arrayEnd = value.lastIndexOf("]")

    if (arrayStart >= 0 && arrayEnd > arrayStart) {
      return JSON.parse(value.slice(arrayStart, arrayEnd + 1))
    }

    const objectStart = value.indexOf("{")
    const objectEnd = value.lastIndexOf("}")

    if (objectStart >= 0 && objectEnd > objectStart) {
      return JSON.parse(value.slice(objectStart, objectEnd + 1))
    }

    throw new Error("No pude detectar un bloque JSON valido en el archivo")
  }
}

const toRawListing = (input: unknown, sourceName: RawListing["source"]): RawListing => {
  if (!input || typeof input !== "object") {
    throw new Error("Item invalido: se esperaba objeto")
  }

  const candidate = input as Record<string, unknown>
  const url = typeof candidate.url === "string" ? candidate.url : typeof candidate.sourceUrl === "string" ? candidate.sourceUrl : ""

  return rawListingSchema.parse({
    source: sourceName,
    externalId: String(candidate.externalId ?? "").trim(),
    url,
    title: String(candidate.title ?? "").trim(),
    priceClp: Number(candidate.priceClp),
    year: Number(candidate.year),
    mileageKm:
      candidate.mileageKm === null || candidate.mileageKm === undefined || candidate.mileageKm === ""
        ? undefined
        : Number(candidate.mileageKm),
    imageUrl: typeof candidate.imageUrl === "string" && candidate.imageUrl.trim() ? candidate.imageUrl : undefined,
    region: typeof candidate.region === "string" ? candidate.region : undefined,
    commune: typeof candidate.commune === "string" ? candidate.commune : undefined,
    sellerName: typeof candidate.sellerName === "string" ? candidate.sellerName : undefined,
    transmissionText:
      typeof candidate.transmissionText === "string"
        ? candidate.transmissionText
        : typeof candidate.transmission === "string"
          ? candidate.transmission
          : undefined,
    fuelText:
      typeof candidate.fuelText === "string"
        ? candidate.fuelText
        : typeof candidate.fuel === "string"
          ? candidate.fuel
          : undefined,
    rawPayload: candidate.rawPayload
  })
}

const getItemsFromPayload = (payload: unknown): unknown[] => {
  if (Array.isArray(payload)) {
    return payload
  }

  if (payload && typeof payload === "object") {
    const record = payload as Record<string, unknown>

    if (Array.isArray(record.items)) {
      return record.items
    }
  }

  throw new Error("Formato no soportado. Esperaba array de listings o { items: [...] }")
}

async function main() {
  const { sourceSlug, filePath } = parseArgs()
  const config = getSourceConfig(sourceSlug)

  const rawText = await readFile(filePath, "utf8")
  const payload = parseJsonLoose(rawText)
  const items = getItemsFromPayload(payload)

  if (items.length === 0) {
    throw new Error("El archivo no contiene listings")
  }

  const rawListings = items.map((item) => toRawListing(item, config.sourceName))

  const startedAt = new Date()
  const crawlRun = await prisma.crawlRun.create({
    data: {
      source: config.sourceName,
      startedAt,
      status: "SUCCESS",
      notes: {
        importMode: "json-file",
        filePath
      }
    }
  })

  try {
    const persisted = await storeRawListingsForSource(sourceSlug, rawListings)

    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: {
          importMode: "json-file",
          filePath,
          storedCount: persisted.fetchedCount - persisted.unavailableCount
        }
      }
    })

    console.log(
      JSON.stringify(
        {
          ok: true,
          source: sourceSlug,
          filePath,
          fetchedCount: persisted.fetchedCount,
          newCount: persisted.newCount,
          updatedCount: persisted.updatedCount,
          unavailableCount: persisted.unavailableCount,
          finishedAt: persisted.finishedAt.toISOString()
        },
        null,
        2
      )
    )
  } catch (error) {
    await prisma.crawlRun.update({
      where: {
        id: crawlRun.id
      },
      data: {
        finishedAt: new Date(),
        status: "FAILED",
        errorMessage: error instanceof Error ? error.message : "Fallo al importar JSON"
      }
    })

    throw error
  }
}

main()
  .catch((error) => {
    console.error(error)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })
