import * as cheerio from "cheerio";
import { Property } from "../types/property";
import { Zone } from "../models/zoneModel";
import { PlaywrightBaseScraper } from "./playwrightBaseScraper";
import { VALENCIA_DISTRICTS } from "../config/valenciaDistricts";
import { ValenciaDistrictModel } from "../models/valenciaDistrictModel";
import { PortalBlockedError, PortalBrowserSession } from "./portalBrowserSession";

/**
 * Scraper para Fotocasa (Playwright con espera inteligente).
 *
 * Fotocasa es un SPA React: el SSR solo renderiza 1 tarjeta, el resto se hidrata
 * vía API interna cuando el browser ejecuta JS. Usamos Playwright con waitForFunction
 * que espera hasta que al menos 5 artículos tengan links reales (no esqueletos).
 *
 * URL venta: /es/comprar/viviendas/{slug}/todas-las-zonas/l
 * URL alquiler: /es/alquiler/viviendas/{slug}/todas-las-zonas/l
 * Paginación: append /p{n} al final
 */
export class FotocasaPlaywrightScraper extends PlaywrightBaseScraper {
    private async getValenciaDistrictTargets(): Promise<Array<{ slug: string; zoneLabel: string }>> {
        try {
            const rows = await ValenciaDistrictModel.findAll();
            if (rows.length > 0) return rows
                .filter(r => r.active !== 0 && !!r.fotocasa_slug)
                .map(r => ({ slug: r.fotocasa_slug, zoneLabel: `${r.name}, Valencia` }));
        } catch { /* fallback */ }
        return VALENCIA_DISTRICTS.map(d => ({ slug: d.fotocasa, zoneLabel: `${d.name}, Valencia` }));
    }

    constructor() {
        super("https://www.fotocasa.es", "Fotocasa");
        this.pageTimeoutMs = 60000;
    }

    async scrape(): Promise<Property[]> {
        const zones = await this.getActiveZones();
        const targets: Array<{ slug: string; zoneLabel?: string; isValenciaDistrict?: boolean }> = [];
        if (zones.length > 0) {
            for (const z of zones) {
                if (z.slug === "valencia") {
                    const districts = await this.getValenciaDistrictTargets();
                    targets.push(...districts.map(d => ({ ...d, isValenciaDistrict: true })));
                } else {
                    targets.push({ slug: z.slug, zoneLabel: z.name });
                }
            }
        } else {
            targets.push({ slug: "lliria", zoneLabel: "Llíria" });
        }

        // Slug mapping: zona DB → slug Fotocasa (nombres valencianos o abreviados)
        const fotocasaSlugMap: Record<string, string> = {
            "eliana": "l-eliana",
            "riba-roja": "riba-roja-de-turia",
            "pobla-de-vallbona": "la-pobla-de-vallbona",
            "villamarchante": "vilamarxant",
        };

        const properties: Property[] = [];
        const seenUrls = new Set<string>();
        const session = new PortalBrowserSession(this.sourceName, this.pageTimeoutMs);
        const maxPages = Math.max(1, Number(process.env.FOTOCASA_MAX_PAGES_PER_ZONE) || 3);

        try {
            targetLoop: for (const t of targets) {
                const slug = t.slug;
                const fotocasaSlug = fotocasaSlugMap[slug] ?? slug;
                const operationPath = this._operationType === "rent" ? "alquiler" : "comprar";
                const locationPath = t.isValenciaDistrict
                    ? `valencia-capital/${fotocasaSlug}`
                    : `${fotocasaSlug}/todas-las-zonas`;
                const basePageUrl = `${this.baseUrl}/es/${operationPath}/viviendas/${locationPath}/l`;

                for (let page = 1; page <= maxPages; page++) {
                    const pageUrl = page === 1 ? basePageUrl : `${basePageUrl}/p${page}`;
                    let html = "";
                    try {
                        html = await this.fetchFotocasaPage(pageUrl, session);
                    } catch (error) {
                        if (error instanceof PortalBlockedError) {
                            console.warn(`[Fotocasa] ${error.message}. Se detiene el portal para no agravar el bloqueo.`);
                            break targetLoop;
                        }
                        console.warn(`[Fotocasa] Error cargando ${pageUrl}: ${(error as Error).message}`);
                        break;
                    }
                    if (!html) break;

                    const localSeen = new Set<string>(seenUrls);
                    let pageProps = this.parseHtml(html, slug, zones, localSeen, t.zoneLabel);
                    // Fallback: SSR/hidratación débil → extraer listado de __initial_props__ / JSON embebido
                    if (pageProps.length < 10) {
                        const jsonSeen = new Set<string>(seenUrls);
                        const fromJson = this.parseInitialPropsJson(html, slug, zones, jsonSeen, t.zoneLabel);
                        if (fromJson.length > pageProps.length) {
                            console.log(
                                `[Fotocasa] JSON fallback: DOM=${pageProps.length} → JSON=${fromJson.length} (${pageUrl})`
                            );
                            pageProps = fromJson;
                            for (const u of jsonSeen) seenUrls.add(u);
                        } else {
                            for (const u of localSeen) seenUrls.add(u);
                        }
                    } else {
                        for (const u of localSeen) seenUrls.add(u);
                    }
                    if (pageProps.length === 0) break;
                    properties.push(...pageProps);
                    if (pageProps.length < 20) break;
                }
            }
        } finally {
            await session.close();
        }

        return this.validateResults(properties);
    }

    /**
     * Carga la página con Playwright y espera a que el SPA React haya hidratado
     * suficientes artículos (>= 5 con links reales, no esqueletos).
     */
    private async fetchFotocasaPage(url: string, session: PortalBrowserSession): Promise<string> {
        return session.fetchHtml(url, {
            waitForSelector: "article h3 a[href]",
            afterNavigation: async page => {
                await page.evaluate(() => window.scrollBy(0, 700));
                await page.waitForTimeout(1_200);
                await page.evaluate(() => window.scrollBy(0, 1200));
                // Esperar hidratación React (SSR solo trae 1 tarjeta)
                await page.waitForFunction(
                    () => document.querySelectorAll("article h3 a[href*='/es/comprar/'], article h3 a[href*='/es/alquiler/']").length >= 3,
                    { timeout: 20_000 }
                ).catch(() => null);
                await page.waitForTimeout(800);
                const count = await page.locator("article h3 a[href]").count().catch(() => 0);
                console.log(`[${this.sourceName}] ${url} → ${count} artículos con link real`);
            },
        });
    }

    private parseHtml(
        html: string,
        slug: string,
        zones: Zone[],
        seenUrls: Set<string>,
        zoneLabel?: string
    ): Property[] {
        const $ = cheerio.load(html);
        const results: Property[] = [];

        $('article').each((_i, el) => {
            try {
                // Solo tarjetas con link real (no esqueletos)
                const href =
                    $(el).find("h3 a[href*='/es/comprar/'], h3 a[href*='/es/alquiler/']").first().attr("href") ||
                    $(el).find("a[href*='/es/comprar/'], a[href*='/es/alquiler/']").first().attr("href") || "";
                if (!href) return;
                const absUrl = href.startsWith("http") ? href : `${this.baseUrl}${href}`;
                if (seenUrls.has(absUrl)) return;
                seenUrls.add(absUrl);

                // Título
                const title = this.sanitizeText(
                    $(el).find("h3").text() || "Propiedad en Lliria"
                );

                // Precio: buscar span/div con patrón "240.000 €"
                let priceRaw = "";
                $(el).find("span, div").each((_j, e) => {
                    const t = $(e).text().trim();
                    if (!priceRaw && /^\d[\d.,\s]*€/.test(t)) priceRaw = t;
                });
                const price = this.parsePrice(priceRaw);
                if (price <= 0) return;

                // Localización: usar el nombre de la zona directamente para evitar capturar
                // texto extra del alt de imagen (p.ej. "Casa en venta en Benaguasil con Aire acondicionado")
                const zoneObj = zones.find(z => z.slug === slug);
                const location = zoneObj?.name || zoneLabel || this.sanitizeText(slug);

                // Habitaciones y superficie
                let rooms = 0, size_m2 = 0;
                $(el).find("li").each((_j, li) => {
                    const txt = $(li).text();
                    const rm = txt.match(/(\d+)\s*habs?/i);
                    if (rm) rooms = parseInt(rm[1]);
                    const sm = txt.match(/(\d+)\s*m²/i);
                    if (sm) size_m2 = parseInt(sm[1]);
                });

                // Imagen
                const image_url =
                    $(el).find("picture img").first().attr("src") ||
                    $(el).find("img").first().attr("src") || "";

                results.push({
                    title, price, location,
                    municipality: zoneLabel && zoneLabel.includes("Valencia") ? "Valencia" : this.detectMunicipality(location + " " + slug, zones),
                    property_type: this.normalizeType(title),
                    rooms, size_m2,
                    url: absUrl,
                    source: this.sourceName,
                    operation_type: this._operationType,
                    image_url,
                });
            } catch { /* skip */ }
        });

        return results;
    }

    /**
     * Fallback cuando el DOM trae pocas tarjetas: lee #__initial_props__ o
     * script[type=application/json] y mapea realEstates / search results.
     */
    private parseInitialPropsJson(
        html: string,
        slug: string,
        zones: Zone[],
        seenUrls: Set<string>,
        zoneLabel?: string
    ): Property[] {
        const $ = cheerio.load(html);
        const raw =
            $("#__initial_props__").first().html() ||
            $("#__initial_props__").first().text() ||
            $('script#__initial_props__').first().html() ||
            $('script#__initial_props__').first().text() ||
            $('script[type="application/json"]').first().html() ||
            $('script[type="application/json"]').first().text() ||
            "";
        if (!raw || raw.length < 50) return [];

        let data: any;
        try {
            data = JSON.parse(raw.trim());
        } catch {
            return [];
        }

        const items = this.extractRealEstateItems(data);
        if (items.length === 0) return [];

        const zoneObj = zones.find(z => z.slug === slug);
        const location = zoneObj?.name || zoneLabel || this.sanitizeText(slug);
        const results: Property[] = [];

        for (const item of items) {
            try {
                const href =
                    item?.detail?.['es-ES']?.url ||
                    item?.detail?.url ||
                    item?.url ||
                    item?.link ||
                    item?.propertyUrl ||
                    "";
                if (!href || typeof href !== "string") continue;
                const absUrl = href.startsWith("http") ? href : `${this.baseUrl}${href.startsWith("/") ? "" : "/"}${href}`;
                if (!/\/es\/(comprar|alquiler)\//i.test(absUrl)) continue;
                if (seenUrls.has(absUrl)) continue;
                seenUrls.add(absUrl);

                const title = this.sanitizeText(
                    item?.title ||
                    item?.advertisement?.title ||
                    item?.propertyType ||
                    item?.type ||
                    "Propiedad en Lliria"
                );

                const priceRaw =
                    item?.price?.[0]?.amount ??
                    item?.price?.amount ??
                    item?.price ??
                    item?.rawPrice ??
                    0;
                const price = typeof priceRaw === "number"
                    ? (priceRaw >= (this._operationType === "rent" ? 50 : 1_000) && priceRaw <= 99_000_000 ? priceRaw : 0)
                    : this.parsePrice(String(priceRaw));
                if (price <= 0) continue;

                const rooms = Number(
                    item?.rooms ?? item?.features?.rooms ?? item?.bedrooms ?? 0
                ) || 0;
                const size_m2 = Number(
                    item?.surface ?? item?.features?.surface ?? item?.size ?? item?.m2 ?? 0
                ) || 0;

                const image_url =
                    item?.multimedia?.[0]?.src ||
                    item?.multimedia?.[0]?.url ||
                    item?.images?.[0]?.url ||
                    item?.images?.[0] ||
                    item?.image ||
                    item?.thumbnail ||
                    "";

                const locFromItem =
                    item?.address?.locations?.join?.(", ") ||
                    item?.address?.city ||
                    item?.location?.city ||
                    item?.location ||
                    location;
                const locStr = this.sanitizeText(
                    typeof locFromItem === "string" ? locFromItem : location
                );

                results.push({
                    title,
                    price,
                    location: locStr || location,
                    municipality: zoneLabel && zoneLabel.includes("Valencia")
                        ? "Valencia"
                        : this.detectMunicipality(`${locStr} ${slug}`, zones),
                    property_type: this.normalizeType(
                        title,
                        typeof item?.propertyType === "string" ? item.propertyType : undefined
                    ),
                    rooms,
                    size_m2,
                    url: absUrl,
                    source: this.sourceName,
                    operation_type: this._operationType,
                    image_url: typeof image_url === "string" ? image_url : "",
                });
            } catch { /* skip item */ }
        }

        return results;
    }

    /** Localiza arrays de anuncios en el árbol JSON de Fotocasa. */
    private extractRealEstateItems(data: any): any[] {
        if (!data || typeof data !== "object") return [];

        const candidates = [
            data.realEstates?.items,
            data.realEstates,
            data.search?.realEstates?.items,
            data.search?.realEstates,
            data.page?.realEstates?.items,
            data.page?.realEstates,
            data.searchResult?.realEstates,
            data.searchResult?.items,
            data.items,
            data.results,
        ];
        for (const c of candidates) {
            if (Array.isArray(c) && c.length > 0) return c;
        }

        // Búsqueda superficial de arrays con shape de anuncio
        const keys = Object.keys(data);
        for (const k of keys) {
            const v = data[k];
            if (Array.isArray(v) && v.length > 5 && this.looksLikeEstate(v[0])) return v;
            if (v && typeof v === "object" && !Array.isArray(v)) {
                for (const k2 of Object.keys(v)) {
                    const arr = (v as any)[k2];
                    if (Array.isArray(arr) && arr.length > 5 && this.looksLikeEstate(arr[0])) return arr;
                }
            }
        }
        return [];
    }

    private looksLikeEstate(item: any): boolean {
        if (!item || typeof item !== "object") return false;
        return (
            item.price !== undefined ||
            item.url !== undefined ||
            item.detail !== undefined ||
            item.propertyType !== undefined ||
            item.surface !== undefined ||
            item.rooms !== undefined
        );
    }
}
