"""Point-in-time 13D/13G ownership filing helpers for residual idle-cash sleeves."""
from __future__ import annotations

import datetime as dt
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Any, Iterable

import pyarrow.parquet as pq

from libs.common.logging import get_logger

logger = get_logger(__name__)


@dataclass(frozen=True)
class OwnershipFilingEntry:
    symbol: str
    filing_date: dt.date
    as_of_date: dt.date
    form_type: str
    owner_name: str | None
    owner_key: str | None
    percent_owned: float | None
    aggregate_shares: float | None
    purpose_text: str | None
    purpose_housekeeping_flag: bool
    activist_flag: bool
    is_amendment: bool
    prior_percent_owned: float | None
    percent_delta_points: float | None
    prior_form_group: str | None
    is_initial_for_owner: bool
    is_13g_to_13d_transition: bool
    ownership_strength_score: int | None


def _coerce_date(value: Any) -> dt.date | None:
    if value is None:
        return None
    if isinstance(value, dt.datetime):
        return value.date()
    if isinstance(value, dt.date):
        return value
    text = str(value).strip()
    if not text:
        return None
    try:
        return dt.date.fromisoformat(text[:10])
    except ValueError:
        return None


def _coerce_float(value: Any) -> float | None:
    if value is None:
        return None
    try:
        return float(value)
    except (TypeError, ValueError):
        return None


def _coerce_bool(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if value is None:
        return False
    text = str(value).strip().lower()
    return text in {"1", "true", "t", "yes", "y"}


class PointInTimeOwnershipCalendar:
    """Latest-known 13D/13G ownership events keyed by filing date."""

    def __init__(self, entries: Iterable[OwnershipFilingEntry]) -> None:
        grouped: dict[dt.date, list[OwnershipFilingEntry]] = {}
        for entry in entries:
            grouped.setdefault(entry.filing_date, []).append(entry)
        self._entries_by_filing_date = {
            filing_date: tuple(
                sorted(
                    filing_entries,
                    key=lambda item: (
                        item.symbol,
                        -(item.percent_delta_points or 0.0),
                        -(item.percent_owned or 0.0),
                        not item.activist_flag,
                    ),
                )
            )
            for filing_date, filing_entries in grouped.items()
        }

    @classmethod
    def from_parquet(cls, path: Path) -> PointInTimeOwnershipCalendar:
        table = pq.read_table(str(path))
        rows = table.to_pylist()
        entries: list[OwnershipFilingEntry] = []
        for row in rows:
            symbol = str(row.get("symbol") or "").strip().upper()
            filing_date = _coerce_date(row.get("filing_date"))
            as_of_date = _coerce_date(row.get("as_of_date") or row.get("filing_date"))
            form_type = str(row.get("form_type") or "").strip().upper()
            if not symbol or filing_date is None or as_of_date is None or not form_type:
                continue
            entries.append(
                OwnershipFilingEntry(
                    symbol=symbol,
                    filing_date=filing_date,
                    as_of_date=as_of_date,
                    form_type=form_type,
                    owner_name=str(row.get("owner_name") or "").strip() or None,
                    owner_key=str(row.get("owner_key") or "").strip().lower() or None,
                    percent_owned=_coerce_float(row.get("percent_owned")),
                    aggregate_shares=_coerce_float(row.get("aggregate_shares")),
                    purpose_text=str(row.get("purpose_text") or "").strip() or None,
                    purpose_housekeeping_flag=_coerce_bool(row.get("purpose_housekeeping_flag")),
                    activist_flag=_coerce_bool(row.get("activist_flag")),
                    is_amendment=_coerce_bool(row.get("is_amendment")),
                    prior_percent_owned=_coerce_float(row.get("prior_percent_owned")),
                    percent_delta_points=_coerce_float(row.get("percent_delta_points")),
                    prior_form_group=str(row.get("prior_form_group") or "").strip().upper() or None,
                    is_initial_for_owner=_coerce_bool(row.get("is_initial_for_owner")),
                    is_13g_to_13d_transition=_coerce_bool(row.get("is_13g_to_13d_transition")),
                    ownership_strength_score=(
                        int(row.get("ownership_strength_score"))
                        if row.get("ownership_strength_score") is not None
                        else None
                    ),
                )
            )
        logger.info(
            "pit_ownership_calendar_loaded",
            path=str(path),
            rows=len(entries),
            filing_dates=len({entry.filing_date for entry in entries}),
            symbols=len({entry.symbol for entry in entries}),
        )
        return cls(entries)

    def get_events_between(
        self,
        *,
        start_filing_date: dt.date,
        end_filing_date: dt.date,
        symbols: Iterable[str] | None = None,
    ) -> list[OwnershipFilingEntry]:
        symbol_filter = {
            str(symbol).strip().upper()
            for symbol in (symbols or [])
            if str(symbol).strip()
        }
        rows: list[OwnershipFilingEntry] = []
        current = start_filing_date
        while current <= end_filing_date:
            for entry in self._entries_by_filing_date.get(current, ()):
                if symbol_filter and entry.symbol not in symbol_filter:
                    continue
                rows.append(entry)
            current += dt.timedelta(days=1)
        return rows


@lru_cache(maxsize=8)
def load_pit_ownership_calendar(path_str: str) -> PointInTimeOwnershipCalendar | None:
    path = Path(path_str)
    if not path.exists():
        logger.info("pit_ownership_calendar_missing", path=str(path))
        return None
    return PointInTimeOwnershipCalendar.from_parquet(path)


__all__ = [
    "OwnershipFilingEntry",
    "PointInTimeOwnershipCalendar",
    "load_pit_ownership_calendar",
]
