# api_client.py (complete updated version)
import requests
import json
import re
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from config import config
import time
from collections import defaultdict


class TweedeKamerAPI:
    def __init__(self):
        self.odata_base_url = "https://gegevensmagazijn.tweedekamer.nl/OData/v4/2.0"
        self.session = requests.Session()
        self.session.headers.update(
            {
                "Accept": "application/json",
                "User-Agent": "Dutch-Political-Compass-Tool/1.0",
            }
        )

    def get_motions(
        self,
        start_date: datetime = None,
        end_date: datetime = None,
        limit: int = 500,
        skip_details: bool = False,
    ) -> List[Dict]:
        """Get motions with voting results using OData API.

        Args:
            skip_details: If True, skip per-motion detail fetching (Zaak/Document/body text).
                          Uses BesluitTekst from the Besluit record instead. Much faster for
                          bulk historical downloads where AI summarisation is not needed.
        """
        if not start_date:
            start_date = datetime.now() - timedelta(days=730)  # 2 years ago

        try:
            # Get voting records
            voting_records, besluit_meta = self._get_voting_records(
                start_date, end_date, limit
            )
            print(f"Fetched {len(voting_records)} voting records from API")

            # Group by Besluit_Id (decision/motion) and get motion details
            motions = self._process_voting_records(
                voting_records, besluit_meta, skip_details=skip_details
            )
            print(f"Processed into {len(motions)} unique motions")

            return motions

        except Exception as e:
            print(f"Error fetching motions from API: {e}")
            return []

    def _get_voting_records(
        self, start_date: datetime, end_date: datetime = None, limit: int = 50000
    ) -> tuple:
        """Get individual voting records from the API via Besluit?$expand=Stemming.

        Uses Besluit (decisions) with embedded Stemming (votes) to avoid the
        expensive per-record pagination of the flat Stemming endpoint.
        Only returns Besluit records with StemmingsSoort set (actual votes).

        Returns:
            (voting_records, besluit_meta) where:
              - voting_records: flat list of Stemming dicts with Besluit_Id set
              - besluit_meta: dict of besluit_id → {title, date, besluit_tekst}
        """

        # Format date properly for OData
        start_date_str = start_date.strftime("%Y-%m-%d")
        filter_query = (
            f"GewijzigdOp ge {start_date_str}T00:00:00Z"
            " and StemmingsSoort ne null"
            " and Verwijderd eq false"
        )

        if end_date:
            end_date_str = end_date.strftime("%Y-%m-%d")
            filter_query += f" and GewijzigdOp le {end_date_str}T23:59:59Z"

        page_size = 250  # API caps $top at 250
        base_url = f"{self.odata_base_url}/Besluit"
        base_params = {
            "$filter": filter_query,
            "$top": page_size,
            "$expand": "Stemming",
            "$orderby": "GewijzigdOp desc",
        }

        all_records: List[Dict] = []
        besluit_meta: Dict[str, Dict] = {}
        skip = 0

        try:
            while len(besluit_meta) < limit:
                params = {**base_params, "$skip": skip}
                response = self.session.get(
                    base_url, params=params, timeout=config.API_TIMEOUT
                )
                response.raise_for_status()
                data = response.json()

                besluit_page = data.get("value", [])
                if not besluit_page:
                    break

                # Flatten: for each Besluit, capture metadata and emit each Stemming record
                for besluit in besluit_page:
                    besluit_id = besluit.get("Id")
                    if not besluit_id:
                        continue

                    date_str = besluit.get("GewijzigdOp", "")
                    date = date_str.split("T")[0] if date_str else ""
                    besluit_meta[besluit_id] = {
                        "title": besluit.get("BesluitTekst")
                        or f"Besluit {besluit_id[:8]}",
                        "date": date,
                        "besluit_tekst": besluit.get("BesluitTekst") or "",
                    }

                    for stemming in besluit.get("Stemming", []):
                        stemming["Besluit_Id"] = besluit_id
                        all_records.append(stemming)

                if len(besluit_page) < page_size:
                    break  # last page
                skip += page_size

            print(
                f"Retrieved {len(all_records)} voting records from {len(besluit_meta)} decisions"
            )
            return all_records, besluit_meta

        except requests.exceptions.RequestException as e:
            print(f"API request failed: {e}")
            if hasattr(e, "response") and e.response is not None:
                print(f"Response status: {e.response.status_code}")
                print(f"Response text: {e.response.text[:500]}")
            return all_records, besluit_meta  # return whatever we got before failure

    def _process_voting_records(
        self,
        records: List[Dict],
        besluit_meta: Dict[str, Dict] = None,
        skip_details: bool = False,
    ) -> List[Dict]:
        """Process individual voting records into grouped motions.

        Args:
            records: Flat Stemming records, each with Besluit_Id set.
            besluit_meta: Pre-fetched dict of besluit_id → {title, date, besluit_tekst}.
                          If provided and skip_details=True, avoids per-motion HTTP calls.
            skip_details: Skip fetching Zaak/Document/body text per motion.
        """
        if besluit_meta is None:
            besluit_meta = {}

        # Group records by Besluit_Id (decision/motion)
        motion_groups = defaultdict(
            lambda: {
                "votes": {},
                "mp_vote_parties": {},
                "besluit_id": None,
                "latest_date": None,
            }
        )

        for record in records:
            besluit_id = record.get("Besluit_Id")
            if not besluit_id:
                continue

            # Extract party and vote information
            party_name = record.get("ActorNaam")
            # Some records have Soort explicitly set to None; guard against that
            vote_type = str(record.get("Soort") or "").lower()
            record_date = record.get("GewijzigdOp", "")

            if not party_name:
                continue

            # Map vote types to our format
            if vote_type == "voor":
                vote = "voor"
            elif vote_type == "tegen":
                vote = "tegen"
            else:
                vote = "afwezig"

            # Store the vote
            motion_groups[besluit_id]["votes"][party_name] = vote
            motion_groups[besluit_id]["besluit_id"] = besluit_id

            # For individual MPs (ActorNaam contains comma), also capture their party
            if "," in party_name:
                actor_fractie = record.get("ActorFractie")
                if actor_fractie:
                    motion_groups[besluit_id]["mp_vote_parties"][party_name] = (
                        actor_fractie
                    )

            # Track the latest date for this motion
            if (
                not motion_groups[besluit_id]["latest_date"]
                or record_date > motion_groups[besluit_id]["latest_date"]
            ):
                motion_groups[besluit_id]["latest_date"] = record_date

        # Now get motion details for each unique Besluit_Id
        motions = []
        for besluit_id, motion_data in motion_groups.items():
            if len(motion_data["votes"]) < 3:  # Skip motions with too few votes
                continue

            # Get motion details — use pre-fetched meta if skip_details=True
            if skip_details and besluit_id in besluit_meta:
                meta = besluit_meta[besluit_id]
                motion_details = {
                    "title": meta["title"],
                    "description": meta["besluit_tekst"] or meta["title"],
                    "date": meta["date"],
                    "externe_identifier": None,
                    "body_text": None,
                }
            else:
                motion_details = self._get_motion_details(besluit_id)

            if not motion_details:
                # Fall back to besluit_meta if available, else generic placeholder
                if besluit_id in besluit_meta:
                    meta = besluit_meta[besluit_id]
                    motion_details = {
                        "title": meta["title"],
                        "description": meta["besluit_tekst"] or meta["title"],
                        "date": meta["date"],
                        "externe_identifier": None,
                        "body_text": None,
                    }
                else:
                    latest = motion_data["latest_date"] or ""
                    motion_details = {
                        "title": f"Motion {besluit_id[:8]}",
                        "description": "No description available",
                        "date": latest.split("T")[0]
                        if latest
                        else datetime.now().strftime("%Y-%m-%d"),
                        "externe_identifier": None,
                        "body_text": None,
                    }

            # Calculate winning margin
            voting_results = motion_data["votes"]
            total_votes = sum(
                1 for vote in voting_results.values() if vote in ["voor", "tegen"]
            )

            if total_votes == 0:
                continue

            votes_for = sum(1 for vote in voting_results.values() if vote == "voor")
            winning_margin = abs(votes_for - (total_votes - votes_for)) / total_votes

            motion = {
                "title": motion_details["title"],
                "description": motion_details["description"],
                "date": motion_details["date"],
                "policy_area": self._determine_policy_area(
                    motion_details["title"], motion_details["description"]
                ),
                "voting_results": voting_results,
                "mp_vote_parties": motion_data["mp_vote_parties"],
                "winning_margin": winning_margin,
                "url": f"https://www.tweedekamer.nl/kamerstukken/stemmingsuitslagen/{besluit_id}",
                "externe_identifier": motion_details.get("externe_identifier"),
                "body_text": motion_details.get("body_text"),
            }

            motions.append(motion)

        return motions

    def _get_motion_details(self, besluit_id: str) -> Optional[Dict]:
        """Get motion details from Besluit endpoint.

        Fetches Zaak.Onderwerp for the human-readable title, then follows the
        Zaak → Document → DocumentVersie chain to get the ExterneIdentifier,
        which is used to scrape the full motion body text from
        zoek.officielebekendmakingen.nl.
        """
        try:
            # Step 1: Besluit → Zaak (title) + Zaak.Id for document lookup
            url = f"{self.odata_base_url}/Besluit({besluit_id})"
            params = {"$expand": "Zaak($select=Id,Onderwerp)"}
            response = self.session.get(url, params=params, timeout=config.API_TIMEOUT)
            response.raise_for_status()
            record = response.json()

            zaak_list = record.get("Zaak", [])
            onderwerp = None
            zaak_id = None
            if zaak_list:
                onderwerp = zaak_list[0].get("Onderwerp")
                zaak_id = zaak_list[0].get("Id")

            besluit_tekst = record.get("BesluitTekst") or ""
            date_str = record.get("GewijzigdOp", "")
            date = (
                date_str.split("T")[0]
                if date_str
                else datetime.now().strftime("%Y-%m-%d")
            )

            title = onderwerp or f"Motion {besluit_id[:8]}"
            description = onderwerp or besluit_tekst or "Geen beschrijving beschikbaar"

            # Step 2: Fetch ExterneIdentifier via Zaak → Document → DocumentVersie
            externe_identifier = None
            body_text = None
            if zaak_id:
                externe_identifier = self._get_externe_identifier(zaak_id)
                if externe_identifier:
                    body_text = self._fetch_body_text(externe_identifier)

            return {
                "title": title,
                "description": body_text or description,
                "date": date,
                "externe_identifier": externe_identifier,
                "body_text": body_text,
            }

        except Exception as e:
            print(f"Error getting motion details for {besluit_id}: {e}")

        return None

    def _get_externe_identifier(self, zaak_id: str) -> Optional[str]:
        """Fetch the ExterneIdentifier for the first non-deleted DocumentVersie of a Zaak."""
        try:
            url = f"{self.odata_base_url}/Zaak({zaak_id})"
            params = {
                "$expand": "Document($expand=DocumentVersie($select=Id,ExterneIdentifier,Extensie,Verwijderd))"
            }
            response = self.session.get(url, params=params, timeout=config.API_TIMEOUT)
            response.raise_for_status()
            data = response.json()

            for doc in data.get("Document", []):
                for versie in doc.get("DocumentVersie", []):
                    if versie.get("Verwijderd"):
                        continue
                    ext_id = versie.get("ExterneIdentifier")
                    if ext_id:
                        return ext_id
        except Exception as e:
            print(f"Error fetching ExterneIdentifier for zaak {zaak_id}: {e}")

        return None

    def _fetch_body_text(self, externe_identifier: str) -> Optional[str]:
        """Scrape full motion body text from zoek.officielebekendmakingen.nl."""
        try:
            url = f"https://zoek.officielebekendmakingen.nl/{externe_identifier}.html"
            response = self.session.get(url, timeout=config.API_TIMEOUT)
            response.raise_for_status()
            html = response.text

            # Strip tags
            text = re.sub(r"<[^>]+>", " ", html)
            text = re.sub(r"&[a-z]+;", " ", text)
            text = re.sub(r"\s+", " ", text).strip()

            # Find the motion body starting at the first relevant keyword
            start_keywords = [
                "constaterende",
                "overwegende",
                "verzoekt",
                "spreekt uit",
                "roept op",
                "de kamer,",
            ]
            start_pos = len(text)
            for kw in start_keywords:
                pos = text.lower().find(kw)
                if pos != -1 and pos < start_pos:
                    start_pos = pos

            if start_pos == len(text):
                return None  # No motion body found

            body = text[start_pos:]

            # Trim at end markers
            end_markers = [
                "gaat over tot de orde van de dag",
                "naar boven",
                "deze motie is",
                "nr.",
            ]
            for marker in end_markers:
                pos = body.lower().find(marker)
                if pos != -1:
                    body = body[:pos]

            body = body.strip()
            return body if len(body) > 50 else None

        except Exception as e:
            print(f"Error fetching body text for {externe_identifier}: {e}")

        return None

    def _determine_policy_area(self, title: str, description: str) -> str:
        """Determine policy area from motion title and description"""
        text = (title + " " + description).lower()

        # Policy area keyword mapping
        policy_mapping = {
            "Economie": [
                "economie",
                "belasting",
                "budget",
                "financiën",
                "werkgelegenheid",
                "bedrijven",
                "economisch",
            ],
            "Klimaat": [
                "klimaat",
                "co2",
                "duurzaam",
                "energie",
                "milieu",
                "uitstoot",
                "klimaatverandering",
            ],
            "Immigratie": [
                "migratie",
                "asiel",
                "vreemdeling",
                "integratie",
                "naturalisatie",
                "immigratie",
            ],
            "Zorg": [
                "zorg",
                "gezondheid",
                "ziekenhuis",
                "medicijn",
                "arts",
                "patiënt",
                "gezondheidszorg",
            ],
            "Onderwijs": [
                "onderwijs",
                "school",
                "universiteit",
                "student",
                "leraar",
                "educatie",
            ],
            "Defensie": [
                "defensie",
                "militair",
                "veiligheid",
                "oorlog",
                "leger",
                "veiligheidsdienst",
            ],
        }

        for area, keywords in policy_mapping.items():
            if any(keyword in text for keyword in keywords):
                return area

        return "Algemeen"

    def test_api_connection(self) -> bool:
        """Test if API is accessible"""
        try:
            url = f"{self.odata_base_url}/Stemming"
            params = {"$top": 1}

            response = self.session.get(url, params=params, timeout=10)
            response.raise_for_status()

            data = response.json()
            return len(data.get("value", [])) > 0

        except Exception as e:
            print(f"API connection test failed: {e}")
            return False
