"""Repository for codal.ir — PostgreSQL only."""

from __future__ import annotations

import uuid
from datetime import datetime, timezone
from typing import Any

from repo_core import BaseRepository

from .model import CodalStatement, CodalPublisher, CodalPublisherDetail, CodalPublisherContact, CodalPublisherBoardMember, CodalPublisherCapitalEvent, CodalPublisherSimilar, CodalPublisherMessage


class CodalStatementRepository(BaseRepository[CodalStatement]):
    """Repository for codal_statements — upsert by URL/version, query by type."""

    TABLE = "codal_statements"
    _backends = {"db"}

    # ------------------------------------------------------------------
    # Upsert
    # ------------------------------------------------------------------

    async def upsert_by_url(self, data: dict[str, Any]) -> CodalStatement:
        """Insert or update a statement by URL and version.

        On first insert, defaults ``version`` to ``1``, sets
        ``discovery_time`` to now (UTC ISO 8601), and defaults ``status``
        to ``"discovered"`` if not provided.
        On update, existing values are preserved unless explicitly
        overwritten.

        Returns the resulting row (either newly inserted or existing).
        """
        url = data.get("url")
        if not url:
            raise ValueError("'url' is required for upsert_by_url")

        version = int(data.get("version") or 1)
        data["version"] = version

        existing = await self.first(self.TABLE, {"url": url, "version": version})
        if existing is not None:
            data["id"] = existing.id
            await self.update_by_id(self.TABLE, existing.id, data)
            return await self.find_by_id(self.TABLE, existing.id)

        # First insert — auto-set discovery metadata
        data.setdefault("id", str(uuid.uuid4()))
        data.setdefault("version", version)
        data.setdefault("discovery_time", datetime.now(timezone.utc).isoformat())
        data.setdefault("status", "discovered")
        return await self.insert_one(self.TABLE, data)

    # ------------------------------------------------------------------
    # Queries
    # ------------------------------------------------------------------

    async def find_by_statement_type(
        self, statement_type: str, limit: int = 1000
    ) -> list[CodalStatement]:
        """Find all statements of a given type."""
        return await self.find(
            self.TABLE,
            {"statement_type": statement_type},
            limit=limit,
        )

    async def list_statement_types(self) -> list[str]:
        """Return all distinct statement_type values."""
        rows = await self.find(self.TABLE, {}, limit=10000)
        types = {row.statement_type for row in rows if row.statement_type}
        return sorted(types)

    async def count_by_type(self) -> dict[str, int]:
        """Return {statement_type: count} for all types."""
        rows = await self.find(self.TABLE, {}, limit=100000)
        counts: dict[str, int] = {}
        for row in rows:
            t = row.statement_type or "(none)"
            counts[t] = counts.get(t, 0) + 1
        return dict(sorted(counts.items(), key=lambda x: -x[1]))

    # ------------------------------------------------------------------
    # Phase 3 queries — publisher, status, statement_id
    # ------------------------------------------------------------------

    async def find_by_publisher_id(
        self, publisher_id: int, limit: int = 1000
    ) -> list[CodalStatement]:
        """Find all statements for a given publisher."""
        return await self.find(
            self.TABLE,
            {"publisher_id": publisher_id},
            limit=limit,
            order_by=["-publish_date"],
        )

    async def find_by_status(
        self, status: str, limit: int = 1000
    ) -> list[CodalStatement]:
        """Find all statements with a given status."""
        return await self.find(
            self.TABLE,
            {"status": status},
            limit=limit,
        )

    async def find_by_statement_id(
        self, statement_id: str
    ) -> CodalStatement | None:
        """Find a statement by its codal.ir statement_id."""
        return await self.first(self.TABLE, {"statement_id": statement_id})

    async def find_by_url(
        self, url: str, version: int = 1
    ) -> CodalStatement | None:
        """Find a statement by its URL and version.

        Tries exact match first, then falls back to with/without
        trailing ``/`` to handle URL normalization differences.
        """
        # 1. Exact match
        criteria = {"url": url, "version": version}
        result = await self.first(self.TABLE, criteria)
        if result is not None:
            return result

        # 2. Try with / without trailing slash
        alt = url[:-1] if url.endswith("/") else url + "/"
        return await self.first(self.TABLE, {"url": alt, "version": version})

    async def find_latest_by_url(
        self, url: str
    ) -> CodalStatement | None:
        """Find the highest-version statement for a given URL (any trailing slash)."""
        alt = url[:-1] if url.endswith("/") else url + "/"
        rows = await self.find(self.TABLE, {}, limit=5000)
        matches = [
            r for r in rows
            if getattr(r, "url", "") in (url, alt)
        ]
        if not matches:
            return None
        return max(matches, key=lambda r: getattr(r, "version", 0))

    async def find_pending_fetch(
        self, limit: int = 100
    ) -> list[CodalStatement]:
        """Find statements that need fetching (status is null or 'unknown')."""
        rows = await self.find(self.TABLE, {}, limit=limit * 2, order_by=["-created_at"])
        return [
            r for r in rows
            if r.status is None or r.status.lower() in ("unknown", "discovered")
        ][:limit]

    async def count_by_status(self) -> dict[str, int]:
        """Return {status: count} for all statements."""
        rows = await self.find(self.TABLE, {}, limit=100000)
        counts: dict[str, int] = {}
        for row in rows:
            s = row.status or "unknown"
            counts[s] = counts.get(s, 0) + 1
        return dict(sorted(counts.items(), key=lambda x: -x[1]))


class CodalPublisherRepository(BaseRepository[CodalPublisher]):
    """Repository for codal_publishers — upsert by publisher_id, query by namad."""

    TABLE = "codal_publishers"
    _backends = {"db"}

    # ------------------------------------------------------------------
    # Upsert
    # ------------------------------------------------------------------

    async def upsert_by_publisher_id(
        self, data: dict[str, Any]
    ) -> CodalPublisher:
        """Insert or update a publisher by its unique publisher_id.

        Returns the resulting row (either newly inserted or existing).
        """
        publisher_id = data.get("publisher_id")
        if publisher_id is None:
            raise ValueError("'publisher_id' is required for upsert_by_publisher_id")

        existing = await self.first(
            self.TABLE, {"publisher_id": publisher_id}
        )
        if existing is not None:
            await self.update_by_id(
                self.TABLE,
                existing.publisher_id,
                data,
                id_field="publisher_id",
            )
            return await self.find_by_id(
                self.TABLE,
                existing.publisher_id,
                id_field="publisher_id",
            )

        return await self.insert_one(self.TABLE, data)

    # ------------------------------------------------------------------
    # Queries
    # ------------------------------------------------------------------

    async def find_by_namad(
        self, namad: str, limit: int = 100
    ) -> list[CodalPublisher]:
        """Find all publishers with a given namad (stock symbol)."""
        return await self.find(
            self.TABLE,
            {"namad": namad},
            limit=limit,
        )


class CodalPublisherDetailRepository(BaseRepository[CodalPublisherDetail]):
    """Repository for codal_publisher_details — upsert by publisher_id."""

    TABLE = "codal_publisher_details"
    _backends = {"db"}

    async def upsert_by_publisher_id(
        self, data: dict[str, Any]
    ) -> CodalPublisherDetail:
        """Insert or update publisher details by publisher_id."""
        publisher_id = data.get("publisher_id")
        if publisher_id is None:
            raise ValueError("'publisher_id' is required")

        existing = await self.first(
            self.TABLE, {"publisher_id": publisher_id}
        )
        if existing is not None:
            await self.update_by_id(
                self.TABLE,
                existing.publisher_id,
                data,
                id_field="publisher_id",
            )
            return await self.find_by_id(
                self.TABLE,
                existing.publisher_id,
                id_field="publisher_id",
            )

        return await self.insert_one(self.TABLE, data)


class CodalPublisherContactRepository(BaseRepository[CodalPublisherContact]):
    """Repository for codal_publisher_contacts — upsert by publisher_id."""

    TABLE = "codal_publisher_contacts"
    _backends = {"db"}

    async def upsert_by_publisher_id(
        self, data: dict[str, Any]
    ) -> CodalPublisherContact:
        publisher_id = data.get("publisher_id")
        if publisher_id is None:
            raise ValueError("'publisher_id' is required")
        existing = await self.first(self.TABLE, {"publisher_id": publisher_id})
        if existing is not None:
            await self.update_by_id(self.TABLE, existing.publisher_id, data, id_field="publisher_id")
            return await self.find_by_id(self.TABLE, existing.publisher_id, id_field="publisher_id")
        return await self.insert_one(self.TABLE, data)


class CodalPublisherBoardMemberRepository(BaseRepository[CodalPublisherBoardMember]):
    """Repository for codal_publisher_board — query by publisher_id."""

    TABLE = "codal_publisher_board"
    _backends = {"db"}

    async def find_by_publisher_id(self, publisher_id: int) -> list[CodalPublisherBoardMember]:
        return await self.find(self.TABLE, {"publisher_id": publisher_id}, limit=100)


class CodalPublisherCapitalEventRepository(BaseRepository[CodalPublisherCapitalEvent]):
    """Repository for codal_publisher_capital — query by publisher_id."""

    TABLE = "codal_publisher_capital"
    _backends = {"db"}

    async def find_by_publisher_id(self, publisher_id: int) -> list[CodalPublisherCapitalEvent]:
        return await self.find(self.TABLE, {"publisher_id": publisher_id}, limit=500)


class CodalPublisherSimilarRepository(BaseRepository[CodalPublisherSimilar]):
    """Repository for codal_publisher_similar — query by publisher_id."""

    TABLE = "codal_publisher_similar"
    _backends = {"db"}

    async def find_by_publisher_id(self, publisher_id: int) -> list[CodalPublisherSimilar]:
        return await self.find(self.TABLE, {"publisher_id": publisher_id}, limit=500)


class CodalPublisherMessageRepository(BaseRepository[CodalPublisherMessage]):
    """Repository for codal_publisher_messages — query by publisher_id."""

    TABLE = "codal_publisher_messages"
    _backends = {"db"}

    async def find_by_publisher_id(self, publisher_id: int) -> list[CodalPublisherMessage]:
        return await self.find(self.TABLE, {"publisher_id": publisher_id}, limit=100)



