"""Rule-based event parser for SEC 8-K/6-K exhibits."""
from __future__ import annotations

import datetime as dt
import re
from typing import Any

from libs.common.time_utils import filing_time_bucket
from libs.schemas.types import (
    ConfidenceOutput,
    EvidenceItem,
    GuidanceOutput,
    ParserEventOutput,
    RiskFlagsOutput,
    SignalsOutput,
)

PARSER_VERSION = "rule-1.0.0"
SCHEMA_VERSION = "1.0.0"

# Item number patterns
_ITEM_RE = re.compile(r"item\s+(\d+\.\d+)", re.IGNORECASE)

# Guidance
_GUIDANCE_RAISED = re.compile(
    r"(guidance raised|above prior outlook|raised.*guidance|raising.*guidance"
    r"|increased.*guidance|raised.*forecast|raising.*forecast"
    r"|above.*expectations|above.*consensus|raised.*outlook|raising.*outlook"
    r"|above.*prior.*guidance|exceed.*guidance)",
    re.IGNORECASE,
)
_GUIDANCE_LOWERED = re.compile(
    r"(guidance lowered|revised down|lowered.*guidance|reduced.*guidance"
    r"|below.*expectations|below.*prior.*guidance|cut.*guidance|lowered.*outlook"
    r"|lowered.*forecast)",
    re.IGNORECASE,
)
_GUIDANCE_MAINTAINED = re.compile(
    r"(reaffirm|maintain.*guidance|in.line.*outlook|inline.*guidance|on.track)",
    re.IGNORECASE,
)
_GUIDANCE_WITHDRAWN = re.compile(
    r"\b(?:withdrawn|withdraw|withdrawing|suspended|suspend|suspending)\b"
    r"(?:\W+\w+){0,3}\W+guidance\b",
    re.IGNORECASE,
)

_EARNINGS_RELEASE_HINT = re.compile(
    r"((?:reports?|reported|announced)\s+(?:its\s+)?(?:fiscal\s+)?"
    r"(?:first|second|third|fourth|q[1-4]|[1-4](?:st|nd|rd|th))?.{0,40}financial results)"
    r"|financial results for (?:its\s+)?(?:fiscal\s+)?"
    r"(?:first|second|third|fourth|q[1-4]|[1-4](?:st|nd|rd|th)).{0,40}quarter",
    re.IGNORECASE,
)

# Signals
_DEMAND_STRONG = re.compile(
    r"(demand remains strong|strong demand|robust demand|record demand"
    r"|demand acceleration|pipeline.*strong|strong.*pipeline|broad.based demand)",
    re.IGNORECASE,
)
_DEMAND_WEAK = re.compile(
    r"(demand softness|weak demand|softer demand|demand weakness"
    r"|elongated sales cycles|slower.*demand)",
    re.IGNORECASE,
)
_PRICING_POWER = re.compile(
    r"(pricing strength|price realization|pricing power|favorable.*pricing"
    r"|pricing.*favorable|price.*increase|raised.*prices)",
    re.IGNORECASE,
)
_PRICING_ABSENT = re.compile(
    r"(pricing pressure|price.*compression|competitive.*pricing|price.*decline)",
    re.IGNORECASE,
)
_BACKLOG_PRESENT = re.compile(
    r"(backlog.*increas|increased.*backlog|bookings.*acceler|record.*backlog"
    r"|strong.*backlog|ARR.*grew|deferred.*revenue.*increas|bookings.*grew)",
    re.IGNORECASE,
)
_CUSTOMER_EXPANSION = re.compile(
    r"(customer.*expand|new.*customer|customer.*addition|customer.*grow"
    r"|expanded.*customer|added.*customer|net.*new.*customer)",
    re.IGNORECASE,
)
_MARGIN_IMPROVING = re.compile(
    r"(margin.*expan|expanding.*margin|margin.*improv|gross.*margin.*increas"
    r"|operating.*margin.*improv|profitability.*improv)",
    re.IGNORECASE,
)
_MARGIN_DETERIORATING = re.compile(
    r"(margin.*compress|margin.*declin|margin.*contract|gross.*margin.*declin"
    r"|operating.*margin.*declin)",
    re.IGNORECASE,
)

# Risk flags
_ONEOFF = re.compile(
    r"(one.time|one.off|non.recurring|special.*charge|restructuring.*charge"
    r"|impairment.*charge|write.down|write.off)",
    re.IGNORECASE,
)
_TAX_BENEFIT = re.compile(
    r"(tax.*benefit|deferred.*tax.*asset|tax.*credit|favorable.*tax)", re.IGNORECASE
)
_VALUATION_GAIN = re.compile(
    r"(fair value.*gain|gain on.*sale|unrealized.*gain|valuation.*gain"
    r"|mark.to.market.*gain)",
    re.IGNORECASE,
)
_NON_GAAP = re.compile(
    r"(non.GAAP|adjusted.*earnings|adjusted.*EPS|adjusted.*EBITDA"
    r"|excluding.*items|excluding.*charges)",
    re.IGNORECASE,
)
_FINANCING = re.compile(
    r"(secondary.*offering|convertible.*note|equity.*offering|debt.*financing"
    r"|new.*shares|dilut)",
    re.IGNORECASE,
)
_LEGAL = re.compile(
    r"(litigation|investigation|SEC.*inquiry|DOJ|legal.*proceeding"
    r"|regulatory.*action|enforcement.*action)",
    re.IGNORECASE,
)


def _extract_item_numbers(text: str) -> list[str]:
    return list(dict.fromkeys(_ITEM_RE.findall(text)))


def _classify_event_type(items: list[str]) -> str:
    if "2.02" in items:
        return "earnings_release"
    if "7.01" in items:
        return "guidance_update"
    if "1.01" in items:
        return "material_contract"
    if "8.01" in items:
        return "other_material_event"
    if "5.02" in items:
        return "management_change"
    if "1.03" in items:
        return "other_material_event"
    return "unknown"


def _infer_event_type_from_text(text: str) -> str:
    if _EARNINGS_RELEASE_HINT.search(text):
        return "earnings_release"
    return "unknown"


def _detect_guidance(text: str) -> GuidanceOutput:
    if _GUIDANCE_WITHDRAWN.search(text):
        return GuidanceOutput(status="withdrawn", scope="unknown", notes="Guidance withdrawn.")
    if _GUIDANCE_RAISED.search(text):
        m = _GUIDANCE_RAISED.search(text)
        return GuidanceOutput(
            status="raised",
            scope="unknown",
            notes=m.group(0) if m else "",
        )
    if _GUIDANCE_LOWERED.search(text):
        m = _GUIDANCE_LOWERED.search(text)
        return GuidanceOutput(
            status="lowered",
            scope="unknown",
            notes=m.group(0) if m else "",
        )
    if _GUIDANCE_MAINTAINED.search(text):
        return GuidanceOutput(
            status="inline_or_maintained", scope="unknown", notes="Guidance maintained."
        )
    return GuidanceOutput(status="not_provided", scope="unknown", notes="")


def _detect_signals(text: str) -> SignalsOutput:
    demand: str
    if _DEMAND_STRONG.search(text):
        demand = "strong"
    elif _DEMAND_WEAK.search(text):
        demand = "weakening"
    else:
        demand = "unknown"

    pricing: str
    if _PRICING_POWER.search(text):
        pricing = "present"
    elif _PRICING_ABSENT.search(text):
        pricing = "absent"
    else:
        pricing = "unknown"

    backlog = "present" if _BACKLOG_PRESENT.search(text) else "unknown"
    customer = "present" if _CUSTOMER_EXPANSION.search(text) else "unknown"

    margin: str
    if _MARGIN_IMPROVING.search(text):
        margin = "improving"
    elif _MARGIN_DETERIORATING.search(text):
        margin = "deteriorating"
    else:
        margin = "unknown"

    return SignalsOutput(
        demand_strength=demand,
        pricing_power=pricing,
        backlog_or_bookings=backlog,
        customer_expansion=customer,
        margin_quality=margin,
    )


def _detect_risk_flags(text: str) -> RiskFlagsOutput:
    return RiskFlagsOutput(
        oneoff_item=bool(_ONEOFF.search(text)),
        tax_benefit=bool(_TAX_BENEFIT.search(text)),
        valuation_gain=bool(_VALUATION_GAIN.search(text)),
        non_gaap_heavy=bool(_NON_GAAP.search(text)),
        financing_related=bool(_FINANCING.search(text)),
        legal_or_regulatory_overhang=bool(_LEGAL.search(text)),
    )


def _classify_direction(
    guidance: GuidanceOutput,
    signals: SignalsOutput,
    risk_flags: RiskFlagsOutput,
) -> str:
    bullish_signals = 0
    bearish_signals = 0

    if guidance.status == "raised":
        bullish_signals += 2
    elif guidance.status == "lowered" or guidance.status == "withdrawn":
        bearish_signals += 2

    if signals.demand_strength == "strong":
        bullish_signals += 1
    elif signals.demand_strength == "weakening":
        bearish_signals += 1

    if signals.pricing_power == "present":
        bullish_signals += 1
    elif signals.pricing_power == "absent":
        bearish_signals += 1

    if signals.margin_quality == "improving":
        bullish_signals += 1
    elif signals.margin_quality == "deteriorating":
        bearish_signals += 1

    if signals.backlog_or_bookings == "present":
        bullish_signals += 1

    if signals.customer_expansion == "present":
        bullish_signals += 1

    if risk_flags.financing_related:
        bearish_signals += 1

    if bullish_signals > bearish_signals + 1:
        return "bullish"
    if bearish_signals > bullish_signals + 1:
        return "bearish"
    if bullish_signals > 0 or bearish_signals > 0:
        return "mixed"
    return "unknown"


def _compute_confidence(
    items: list[str],
    guidance: GuidanceOutput,
    signals: SignalsOutput,
    risk_flags: RiskFlagsOutput,
) -> ConfidenceOutput:
    event_type_conf = 0.9 if items else 0.4
    guidance_conf = 0.0 if guidance.status in ("not_provided", "unclear") else 0.8
    direction_conf = 0.5

    known_signals = sum(
        1
        for v in [
            signals.demand_strength,
            signals.pricing_power,
            signals.backlog_or_bookings,
            signals.customer_expansion,
            signals.margin_quality,
        ]
        if v != "unknown"
    )
    direction_conf = min(0.9, 0.3 + known_signals * 0.12)

    risk_count = sum(
        [
            risk_flags.oneoff_item,
            risk_flags.tax_benefit,
            risk_flags.valuation_gain,
            risk_flags.non_gaap_heavy,
            risk_flags.financing_related,
            risk_flags.legal_or_regulatory_overhang,
        ]
    )
    risk_conf = max(0.2, 1.0 - risk_count * 0.1)

    overall = (event_type_conf * 0.3 + direction_conf * 0.4 + guidance_conf * 0.2 + risk_conf * 0.1)

    return ConfidenceOutput(
        overall=round(overall, 3),
        event_type=round(event_type_conf, 3),
        event_direction=round(direction_conf, 3),
        guidance=round(guidance_conf, 3),
        risk_flags=round(risk_conf, 3),
    )


def _build_evidence(text: str, guidance: GuidanceOutput, signals: SignalsOutput) -> list[EvidenceItem]:
    evidence: list[EvidenceItem] = []
    if guidance.notes:
        evidence.append(
            EvidenceItem(
                label="guidance_signal",
                text_span=guidance.notes[:200],
                section_hint="guidance",
                confidence=0.8,
            )
        )
    for pattern, label in [
        (_DEMAND_STRONG, "demand_strong"),
        (_BACKLOG_PRESENT, "backlog_present"),
        (_CUSTOMER_EXPANSION, "customer_expansion"),
        (_MARGIN_IMPROVING, "margin_improving"),
    ]:
        m = pattern.search(text)
        if m:
            evidence.append(
                EvidenceItem(
                    label=label,
                    text_span=m.group(0)[:200],
                    section_hint="body",
                    confidence=0.7,
                )
            )
    return evidence[:10]


class RuleBasedParser:
    """Deterministic rule-based parser for SEC 8-K/6-K documents."""

    def parse(
        self,
        document_id: str,
        form_type: str,
        text: str,
        metadata: dict[str, Any] | None = None,
    ) -> ParserEventOutput:
        metadata = metadata or {}
        filing_date_str = metadata.get("filing_date", dt.date.today().isoformat())
        accepted_at_str = metadata.get("accepted_at_utc")

        # Determine filing time bucket
        time_bucket = "unknown"
        if accepted_at_str:
            try:
                import datetime as dt2
                accepted_dt = dt2.datetime.fromisoformat(accepted_at_str.replace("Z", "+00:00"))
                time_bucket = filing_time_bucket(accepted_dt)
            except Exception:
                time_bucket = "unknown"

        items = metadata.get("item_numbers") or _extract_item_numbers(text)
        event_type = _classify_event_type(items)
        if event_type == "unknown":
            event_type = _infer_event_type_from_text(text)
        guidance = _detect_guidance(text)
        signals = _detect_signals(text)
        risk_flags = _detect_risk_flags(text)
        direction = _classify_direction(guidance, signals, risk_flags)
        confidence = _compute_confidence(items, guidance, signals, risk_flags)
        evidence = _build_evidence(text, guidance, signals)

        # Build summary from first 500 chars
        first_para = text[:500].strip().replace("\n", " ")
        summary = first_para if first_para else f"{form_type} document, event: {event_type}"

        warnings: list[str] = []
        if event_type == "unknown":
            warnings.append("Could not determine event type from item numbers or content.")
        if direction == "unknown":
            warnings.append("Could not determine event direction from content signals.")

        return ParserEventOutput(
            schema_version=SCHEMA_VERSION,
            document_id=document_id,
            parser_kind="rule",
            event_type=event_type,
            event_direction=direction,
            event_date=filing_date_str,
            filing_time_bucket=time_bucket,
            headline=summary[:120],
            summary=summary,
            guidance=guidance,
            signals=signals,
            risk_flags=risk_flags,
            evidence=evidence,
            confidence=confidence,
            warnings=warnings,
        )
