import * as cheerio from "cheerio";
import { BaseScraper } from "./baseScraper";
import { Property } from "../types/property";
import { VALENCIA_DISTRICTS } from "../config/valenciaDistricts";
import { ValenciaDistrictModel } from "../models/valenciaDistrictModel";
import { PortalBlockedError, PortalBrowserSession } from "./portalBrowserSession";

// Slugs especiales: zona.slug → slug de Idealista (sin sufijo de provincia)
const idealistaSlugMap: Record<string, string> = {
    "eliana": "l-eliana",
    "riba-roja": "riba-roja-de-turia",
    "pobla-de-vallbona": "la-pobla-de-vallbona",
    "villamarchante": "vilamarxant",
};

// Sufijo de provincia por zona (por defecto: "-valencia")
const idealistaProvinceSuffix: Record<string, string> = {
    "moncofa": "-castellon",
};

// Zonas que se omiten en Idealista (demasiado genéricas o sin página propia)
const idealistaSkip = new Set<string>(["valencia"]);

export class IdealistaPlaywrightScraper extends BaseScraper {
    private readonly pageTimeoutMs = 45000;
    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.idealista_slug)
                .map(r => ({ slug: r.idealista_slug, zoneLabel: `${r.name}, Valencia` }));
        } catch { /* fallback to static */ }
        return VALENCIA_DISTRICTS.map(d => ({ slug: d.idealista, zoneLabel: `${d.name}, Valencia` }));
    }

    constructor() {
        super("https://www.idealista.com", "Idealista");
    }

    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" });
        }

        const properties: Property[] = [];
        const seenUrls = new Set<string>();

        const session = new PortalBrowserSession(this.sourceName, this.pageTimeoutMs);
        try {
            for (const t of targets) {
                const slug = t.slug;
                if (idealistaSkip.has(slug)) {
                    console.log(`[Idealista] Omitiendo ${slug} (zona muy amplia, sin página específica)`);
                    continue;
                }
                const idealistaSlug = idealistaSlugMap[slug] ?? slug;
                const opPath = this._operationType === "rent" ? "alquiler-viviendas" : "venta-viviendas";
                const url = t.isValenciaDistrict
                    ? `${this.baseUrl}/${opPath}/valencia/${idealistaSlug}/`
                    : `${this.baseUrl}/${opPath}/${idealistaSlug}${idealistaProvinceSuffix[slug] ?? "-valencia"}/`;

                try {
                    const props = await this.scrapeZone(session, url, zones, seenUrls, t.zoneLabel);
                    properties.push(...props);
                } catch (error) {
                    if (error instanceof PortalBlockedError) {
                        console.warn(`[Idealista] ${error.message}. Se detiene el portal para no agravar el bloqueo.`);
                        break;
                    }
                    throw error;
                }
            }
        } catch (err: any) {
            console.error(`[Idealista] Error general: ${err.message}`);
        } finally {
            await session.close();
        }

        return this.validateResults(properties);
    }

    /** Carga una URL dentro de la misma sesión para conservar cookies e identidad. */
    private async loadPage(session: PortalBrowserSession, url: string): Promise<string> {
        try {
            const html = await session.fetchHtml(url, { waitForSelector: "article.item" });
            return html.includes("article") ? html : "";
        } catch (err: any) {
            if (err instanceof PortalBlockedError) throw err;
            console.warn(`[Idealista] Error cargando ${url}: ${err.message}`);
            return "";
        }
    }

    /** Extrae el número máximo de página del HTML */
    private getLastPage(html: string): number {
        // Idealista pagina con url .../pagina-N — buscamos el máximo en los links del paginador
        const matches = html.match(/pagina-(\d+)/g) || [];
        const nums = matches.map(m => parseInt(m.replace("pagina-", ""), 10));
        return nums.length > 0 ? Math.max(...nums) : 1;
    }

    /**
     * Extrae la dirección limpia del título de Idealista.
     * Ej: "Piso en venta en Calle Mayor, Llíria" → "Calle Mayor, Llíria"
     */
    private extractAddress(fullTitle: string): string {
        return fullTitle
            .replace(/^(piso|casa|chalet|d\u00faplex|\u00e1tico|apartamento|estudio|local|garaje|terreno)\s+en\s+(venta|alquiler)\s+en\s+/i, "")
            .trim() || fullTitle;
    }

    /** Parsea los article.item del HTML y devuelve las propiedades */
    private parseItems(
        html: string,
        zones: Awaited<ReturnType<typeof this.getActiveZones>>,
        seenUrls: Set<string>,
        zoneLabel?: string
    ): Property[] {
        const props: Property[] = [];
        const $ = cheerio.load(html);

        $("article.item").each((_i, el) => {
            try {
                const linkEl = $(el).find("a.item-link");
                const fullTitle = this.sanitizeText(linkEl.attr("title") || linkEl.text());
                const href = linkEl.attr("href") || "";
                const absUrl = href.startsWith("http") ? href : `${this.baseUrl}${href}`;

                // Precio: intentar selector específico, luego genérico
                const priceText = this.sanitizeText(
                    $(el).find(".item-price h2, .price-row .item-price, .item-price").first().text()
                );
                const price = this.parsePrice(priceText);

                // Habitaciones y m² buscando por texto del detalle (más robusto que por posición)
                let rooms = 0;
                let size_m2 = 0;
                $(el).find(".item-detail").each((_j, detEl) => {
                    const t = $(detEl).text().trim();
                    if (!rooms && /hab/i.test(t)) rooms = parseInt(t) || 0;
                    if (!size_m2 && /m\u00b2/i.test(t)) size_m2 = parseInt(t) || 0;
                });

                const image_url = $(el).find("picture img").attr("src")
                    || $(el).find("img").first().attr("src") || "";

                // Dirección limpia: quitar «Piso en venta en» del inicio
                const location = this.extractAddress(fullTitle);

                if (price > 0 && fullTitle && !seenUrls.has(absUrl)) {
                    seenUrls.add(absUrl);
                    props.push({
                        title: fullTitle,
                        price,
                        location,
                        municipality: zoneLabel && zoneLabel.includes("Valencia") ? "Valencia" : this.detectMunicipality(location, zones),
                        property_type: this.normalizeType(fullTitle),
                        operation_type: this._operationType,
                        rooms, size_m2,
                        url: absUrl, source: this.sourceName, image_url,
                    });
                }
            } catch { /* skip malformed */ }
        });

        return props;
    }

    private async scrapeZone(
        session: PortalBrowserSession,
        baseZoneUrl: string,
        zones: Awaited<ReturnType<typeof this.getActiveZones>>,
        seenUrls: Set<string>,
        zoneLabel?: string
    ): Promise<Property[]> {
        const props: Property[] = [];

        // Página 1
        const html1 = await this.loadPage(session, baseZoneUrl);
        if (!html1) {
            console.warn(`[Idealista] Sin artículos en ${baseZoneUrl}`);
            return props;
        }

        const page1Props = this.parseItems(html1, zones, seenUrls, zoneLabel);
        props.push(...page1Props);

        const lastPage = this.getLastPage(html1);
        console.log(`[Idealista] ${baseZoneUrl} → ${page1Props.length} props (pág 1/${lastPage})`);

        // Páginas 2..lastPage (máx 5 para evitar demasiadas peticiones)
        const configuredMax = Math.max(1, Number(process.env.IDEALISTA_MAX_PAGES_PER_ZONE) || 3);
        const maxPages = Math.min(lastPage, configuredMax);
        for (let p = 2; p <= maxPages; p++) {
            const pageUrl = `${baseZoneUrl}pagina-${p}.htm`;
            const htmlN = await this.loadPage(session, pageUrl);
            if (!htmlN) break;

            const pageProps = this.parseItems(htmlN, zones, seenUrls, zoneLabel);
            props.push(...pageProps);
            console.log(`[Idealista]   pág ${p}/${lastPage} → ${pageProps.length} props adicionales`);
        }

        return props;
    }
}
