from __future__ import annotations

import html
import re
from dataclasses import dataclass
from datetime import datetime, timezone
from urllib.request import Request, urlopen
from zoneinfo import ZoneInfo

from investly.evaluation.pit_fundamentals import PITFundamentalsSnapshot

_RIYADH = ZoneInfo("Asia/Riyadh")


def _text(raw_html: str) -> str:
    value = re.sub(r"<script\b[^>]*>.*?</script>", " ", raw_html, flags=re.I | re.S)
    value = re.sub(r"<style\b[^>]*>.*?</style>", " ", value, flags=re.I | re.S)
    value = re.sub(r"<[^>]+>", " ", value)
    value = html.unescape(value)
    return re.sub(r"\s+", " ", value).strip()


def _number(value: str) -> float | None:
    cleaned = value.replace(",", "").replace("%", "").strip()
    if cleaned in {"", "-", "—", "N/A", "n/a"}:
        return None
    try:
        return float(cleaned)
    except ValueError:
        return None


def _metric(text: str, label: str) -> tuple[float | None, float | None]:
    pattern = re.compile(
        rf"{re.escape(label)}\s+([\d,.-]+)\s+([\d,.-]+)\s+([\d,.-]+)",
        flags=re.I,
    )
    matches = list(pattern.finditer(text))
    if not matches:
        return None, None
    # Interim Saudi Exchange disclosures often contain a quarter table followed by a
    # cumulative-period table. The final occurrence is the cumulative figure needed
    # for annualisation. Annual/Q1 disclosures normally have one relevant occurrence.
    match = matches[-1]
    return _number(match.group(1)), _number(match.group(3))


def _single_metric(text: str, label: str) -> float | None:
    pattern = re.compile(rf"{re.escape(label)}\s+([\d,.-]+)", flags=re.I)
    matches = list(pattern.finditer(text))
    return _number(matches[-1].group(1)) if matches else None


def _publication_time(text: str) -> datetime:
    match = re.search(r"\b(\d{2}/\d{2}/\d{4})\s+(\d{2}:\d{2}:\d{2})\b", text)
    if match is None:
        raise ValueError("Saudi Exchange announcement publication timestamp not found")
    local = datetime.strptime(
        f"{match.group(1)} {match.group(2)}", "%d/%m/%Y %H:%M:%S"
    ).replace(tzinfo=_RIYADH)
    return local.astimezone(timezone.utc)


def _period_end(text: str) -> str:
    iso = re.search(r"\b(20\d{2}-\d{2}-\d{2})\b", text)
    if iso:
        return iso.group(1)
    dmy = re.search(
        r"period (?:ended|ending).*?\b(\d{1,2})\s+([A-Za-z]+)\s+(20\d{2})\b",
        text,
        re.I,
    )
    if dmy:
        parsed = datetime.strptime(
            f"{dmy.group(1)} {dmy.group(2)} {dmy.group(3)}", "%d %B %Y"
        )
        return parsed.date().isoformat()
    raise ValueError("financial-result period end not found")


def _period_factor(text: str) -> float:
    lower = text.lower()
    if "annual financial" in lower or "current year" in lower:
        return 1.0
    if "nine months" in lower:
        return 4.0 / 3.0
    if "six months" in lower:
        return 2.0
    if "three months" in lower or "current quarter" in lower:
        return 4.0
    return 1.0


@dataclass(frozen=True)
class ParsedSaudiFiling:
    snapshot: PITFundamentalsSnapshot
    raw_eps_period: float | None
    raw_equity_million: float | None


def parse_financial_results_announcement(
    raw_html: str,
    *,
    symbol: str,
    source_url: str,
) -> ParsedSaudiFiling:
    """Parse standard Saudi Exchange financial-result disclosure tables.

    Only values explicitly present in the issuer announcement are used. The publication
    timestamp, not the accounting period end, controls when the snapshot becomes visible
    to replay. Unsupported/missing fields remain None and therefore cannot inflate score.
    """
    text = _text(raw_html)
    published_at = _publication_time(text)
    period_end = _period_end(text)
    factor = _period_factor(text)

    _revenue, revenue_growth_pct = _metric(text, "Sales/Revenue")
    net_income, earnings_growth_pct = _metric(
        text, "Net Profit (Loss) Attributable to Shareholders of the Issuer"
    )
    if net_income is None:
        net_income, earnings_growth_pct = _metric(text, "Net Profit (Loss)")
    equity = _single_metric(text, "Total Shareholders Equity (after Deducting Minority Equity)")
    eps_period = _single_metric(text, "Profit (Loss) per Share")

    eps_ttm = eps_period * factor if eps_period is not None else None
    shares_million = None
    if net_income is not None and eps_period is not None and eps_period != 0:
        shares_million = net_income / eps_period
    book_value_per_share = None
    if equity is not None and shares_million is not None and shares_million > 0:
        book_value_per_share = equity / shares_million

    roe = None
    if net_income is not None and equity is not None and equity > 0:
        roe = (net_income * factor) / equity

    snapshot = PITFundamentalsSnapshot(
        symbol=symbol,
        period_end=period_end,
        published_at=published_at,
        eps_ttm=eps_ttm,
        book_value_per_share=book_value_per_share,
        roe=roe,
        revenue_growth=(revenue_growth_pct / 100 if revenue_growth_pct is not None else None),
        earnings_growth=(earnings_growth_pct / 100 if earnings_growth_pct is not None else None),
        source="saudi-exchange-announcement",
        source_url=source_url,
    )
    return ParsedSaudiFiling(
        snapshot=snapshot,
        raw_eps_period=eps_period,
        raw_equity_million=equity,
    )


def fetch_and_parse_financial_results(
    url: str,
    *,
    symbol: str,
    timeout: int = 20,
) -> ParsedSaudiFiling:
    if not url.startswith("https://www.saudiexchange.sa/"):
        raise ValueError("only official Saudi Exchange announcement URLs are accepted")
    request = Request(url, headers={"User-Agent": "Mozilla/5.0 Investly/0.2"})
    with urlopen(request, timeout=timeout) as response:
        raw = response.read().decode("utf-8", errors="replace")
    return parse_financial_results_announcement(raw, symbol=symbol, source_url=url)
