from __future__ import annotations

import re
from datetime import datetime
from decimal import Decimal, InvalidOperation

import structlog
from bs4 import BeautifulSoup, Tag

from agrobr.constants import Fonte
from agrobr.exceptions import ParseError
from agrobr.models import Indicador

logger = structlog.get_logger()

UNIDADES = {
    "soja": "BRL/sc60kg",
    "soja_parana": "BRL/sc60kg",
    "milho": "BRL/sc60kg",
    "boi": "BRL/@",
    "boi_gordo": "BRL/@",
    "cafe": "BRL/sc60kg",
    "cafe_arabica": "BRL/sc60kg",
    "algodao": "cBRL/lb",
    "trigo": "BRL/ton",
    "arroz": "BRL/sc50kg",
    "acucar": "BRL/sc50kg",
    "acucar_refinado": "BRL/sc50kg",
    "etanol_hidratado": "BRL/L",
    "etanol_anidro": "BRL/L",
    "frango_congelado": "BRL/kg",
    "frango_resfriado": "BRL/kg",
    "suino": "BRL/kg",
    "leite": "BRL/L",
    "laranja_industria": "BRL/cx40.8kg",
    "laranja_in_natura": "BRL/cx40.8kg",
}

PRACAS = {
    "soja": "Paranaguá/PR",
    "soja_parana": "Paraná",
    "milho": "Campinas/SP",
    "boi": "São Paulo/SP",
    "boi_gordo": "São Paulo/SP",
    "cafe": "São Paulo/SP",
    "cafe_arabica": "São Paulo/SP",
    "algodao": "São Paulo/SP",
    "trigo": None,
    "arroz": "Rio Grande do Sul",
    "acucar": "São Paulo/SP",
    "acucar_refinado": "São Paulo/SP",
    "etanol_hidratado": "São Paulo/SP",
    "etanol_anidro": "São Paulo/SP",
    "frango_congelado": "São Paulo/SP",
    "frango_resfriado": "São Paulo/SP",
    "suino": "São Paulo/SP",
    "leite": None,
    "laranja_industria": "São Paulo/SP",
    "laranja_in_natura": "São Paulo/SP",
}


def _parse_date(date_str: str) -> tuple[datetime, bool] | None:
    date_str = date_str.strip()

    match = re.match(r"(\d{2})/(\d{2})/(\d{4})", date_str)
    if match:
        day, month, year = match.groups()
        try:
            return datetime(int(year), int(month), int(day)), False
        except ValueError:
            return None

    weekly = re.match(r"\d{2}\s*-\s*(\d{2})/(\d{2})/(\d{4})", date_str)
    if weekly:
        day, month, year = weekly.groups()
        try:
            return datetime(int(year), int(month), int(day)), True
        except ValueError:
            return None

    return None


def _parse_valor(valor_str: str) -> Decimal | None:
    valor_str = valor_str.strip()

    valor_str = re.sub(r"R\$\s*", "", valor_str)

    valor_str = valor_str.replace(".", "").replace(",", ".")

    try:
        return Decimal(valor_str)
    except InvalidOperation:
        return None


def _parse_variacao(var_str: str) -> Decimal | None:
    var_str = var_str.strip()

    var_str = re.sub(r"[%\s]", "", var_str)

    var_str = var_str.replace(",", ".")

    try:
        return Decimal(var_str)
    except InvalidOperation:
        return None


def _extract_parent_date(table: Tag) -> tuple[datetime, bool] | None:
    cotacao_div = table.find_parent("div", class_="cotacao")
    if not cotacao_div:
        return None
    fechamento = cotacao_div.find("div", class_="fechamento")
    if not fechamento:
        return None
    date_match = re.search(r"(\d{2}/\d{2}/\d{4})", fechamento.get_text())
    if not date_match:
        return None
    return _parse_date(date_match.group(1))


def parse_indicador(html: str, produto: str) -> list[Indicador]:
    soup = BeautifulSoup(html, "lxml")
    indicadores: list[Indicador] = []

    produto_lower = produto.lower()
    unidade = UNIDADES.get(produto_lower, "BRL/unidade")
    praca = PRACAS.get(produto_lower)

    tables = soup.find_all("table", class_="cot-fisicas")

    if not tables:
        tables = soup.find_all("table")

    for table in tables:
        headers = table.find_all("th")
        header_text = " ".join(h.get_text(strip=True).lower() for h in headers)

        has_date_col = "data" in header_text or "vencimento" in header_text
        has_state_col = "estado" in header_text
        has_valor = "valor" in header_text or "r$" in header_text
        has_region_header = "regi" in header_text or has_state_col

        if not has_date_col and not has_state_col:
            continue

        if not has_valor:
            continue

        parent_date: tuple[datetime, bool] | None = None
        if not has_date_col:
            parent_date = _extract_parent_date(table)
            if parent_date is None:
                continue

        has_region_col = (produto_lower == "trigo") or has_region_header

        tbody = table.find("tbody")
        rows = tbody.find_all("tr") if tbody else table.find_all("tr")[1:]

        for row in rows:
            cells = row.find_all("td")

            if len(cells) < 2:
                continue

            if has_date_col:
                data_str = cells[0].get_text(strip=True)
                parsed = _parse_date(data_str)
                if has_region_col and len(cells) >= 3:
                    regiao = cells[1].get_text(strip=True)
                    valor_str = cells[2].get_text(strip=True)
                    var_idx = 3
                else:
                    regiao = None
                    valor_str = cells[1].get_text(strip=True)
                    var_idx = 2
            else:
                parsed = parent_date
                data_str = f"{parsed[0]:%d/%m/%Y}" if parsed else ""
                regiao = cells[0].get_text(strip=True)
                valor_str = cells[1].get_text(strip=True)
                var_idx = 2

            if not valor_str:
                continue

            valor = _parse_valor(valor_str)

            if parsed is None or valor is None:
                logger.warning(
                    "parse_row_failed",
                    source="noticias_agricolas",
                    data_str=data_str,
                    valor_str=valor_str,
                )
                continue

            data, is_weekly = parsed

            meta: dict[str, str | float] = {}
            if is_weekly:
                meta["tipo"] = "media_semanal"
                meta["periodo"] = data_str

            if len(cells) > var_idx:
                var_str = cells[var_idx].get_text(strip=True)
                variacao = _parse_variacao(var_str)
                if variacao is not None:
                    meta["variacao_percentual"] = float(variacao)

            meta["fonte_original"] = "CEPEA/ESALQ"
            meta["via"] = "Notícias Agrícolas"

            row_praca = regiao if regiao else praca

            indicador = Indicador(
                fonte=Fonte.NOTICIAS_AGRICOLAS,
                produto=produto_lower,
                praca=row_praca,
                data=data.date(),
                valor=valor,
                unidade=unidade,
                metodologia="CEPEA/ESALQ via Notícias Agrícolas",
                meta=meta,
                parser_version=2,
                anomalies=["media_semanal"] if is_weekly else [],
            )

            indicadores.append(indicador)

    if not indicadores:
        has_tables = bool(soup.find_all("table"))
        raise ParseError(
            source="noticias_agricolas",
            parser_version=1,
            reason=(
                f"No indicators found for '{produto}'. "
                f"{'Tables found but no data rows matched expected format.' if has_tables else 'No tables found in HTML.'}"
            ),
            html_snippet=html[:500],
        )

    logger.info(
        "parse_complete",
        source="noticias_agricolas",
        produto=produto,
        count=len(indicadores),
    )

    return indicadores
