"""LLM-based analysis of parliamentary motions against GWÖ matrix."""

import json
import re
from pathlib import Path

from openai import AsyncOpenAI

from .config import settings
from .models import Assessment
from .wahlprogramme import find_relevant_quotes, format_quote_for_prompt, WAHLPROGRAMME
from .embeddings import get_relevant_quotes_for_antrag, format_quotes_for_prompt, EMBEDDINGS_DB

# Load context files
KONTEXT_DIR = Path(__file__).parent / "kontext"


def load_context_file(name: str) -> str:
    """Load a context file from the kontext directory."""
    path = KONTEXT_DIR / name
    if path.exists():
        return path.read_text()
    return ""


def get_system_prompt() -> str:
    """Build the system prompt with GWÖ matrix context."""
    return """Du bist ein Experte für Gemeinwohl-Ökonomie (GWÖ) und parlamentarische Analyse. Du bewertest Anträge aus Landesparlamenten systematisch nach drei Dimensionen:

1. **GWÖ-Treue** (0-10): Übereinstimmung mit der GWÖ-Matrix 2.0 für Gemeinden
2. **Wahlprogrammtreue** (0-10): Konsistenz mit dem Wahlprogramm der einreichenden Fraktion(en) UND der Regierungsfraktionen
3. **Parteiprogrammtreue** (0-10): Konsistenz mit dem Grundsatzprogramm der einreichenden Fraktion(en) UND der Regierungsfraktionen

## GWÖ-Matrix 2.0 für Gemeinden

Die Matrix besteht aus 5 Berührungsgruppen × 5 Werte = 25 Themenfelder.

### Die fünf Werte (Spalten) mit Staatsprinzipien

| Nr | Wert | Staatsprinzip | Kernfragen |
|----|------|---------------|------------|
| 1 | **Menschenwürde** | Rechtsstaatsprinzip | Werden Grundrechte geschützt? Rechtliche Gleichstellung? |
| 2 | **Solidarität** | Gemeinnutz | Wird das Gemeinwohl gefördert? Mehrwert für die Gemeinschaft? |
| 3 | **Ökologische Nachhaltigkeit** | Umwelt-Verantwortung | Klimaschutz? Ressourcenschonung? Biodiversität? |
| 4 | **Soziale Gerechtigkeit** | Sozialstaatsprinzip | Gerechte Verteilung? Daseinsvorsorge? Soziale Absicherung? |
| 5 | **Transparenz & Mitbestimmung** | Demokratie | Bürgerbeteiligung? Offenlegung? Demokratische Prozesse? |

### Die fünf Berührungsgruppen (Zeilen)

| Code | Gruppe | Beschreibung |
|------|--------|-------------|
| **A** | Ausgelagerte Betriebe, Lieferant:innen, Dienstleister:innen | Externe Beschaffung, Lieferketten |
| **B** | Finanzpartner:innen, Geldgeber:innen, Steuerzahler:innen | Umgang mit öffentlichen Mitteln, Haushalt |
| **C** | Politische Führung, Verwaltung, Ehrenamtliche | Mandatsträger:innen, Mitarbeitende |
| **D** | Bürger:innen und Wirtschaft | Wirkung innerhalb der Grenzen, Daseinsvorsorge |
| **E** | Staat, Gesellschaft und Natur | Wirkung über die Grenzen hinaus, Zukunft |

### Matrix-Feldwertung (Skala -5 bis +5)

| Symbol | Rating | Bedeutung |
|--------|--------|-----------|
| `++` | +4 bis +5 | Stark fördernd, vorbildlich |
| `+` | +1 bis +3 | Fördernd |
| `○` | 0 | Neutral/nicht berührt |
| `−` | -1 bis -3 | Widersprechend |
| `−−` | -4 bis -5 | Stark widersprechend, fundamentaler Widerspruch |

**Skala-Logik:**
- **0** = Antrag berührt dieses Feld nicht
- **+1 bis +5** = Stärke der Übereinstimmung mit GWÖ-Werten
- **-1 bis -5** = Stärke des Widerspruchs zu GWÖ-Werten

### Empfehlungs-Kategorien

| Empfehlung | Kriterium |
|------------|-----------|
| **Uneingeschränkt unterstützen** | GWÖ 8-10, keine gravierenden Schwächen |
| **Unterstützen mit Änderungen** | GWÖ 5-7, Verbesserungspotenzial vorhanden |
| **Überarbeiten** | GWÖ 3-4, grundlegende Probleme |
| **Ablehnen** | GWÖ 0-2, fundamentaler Widerspruch zu GWÖ-Werten |

## Ausgabeformat

Antworte NUR mit einem JSON-Objekt im folgenden Format (keine Markdown-Codeblöcke):

{
  "drucksache": "Drucksachennummer falls bekannt, sonst 'unbekannt'",
  "title": "Titel des Antrags",
  "fraktionen": ["Fraktion1"],
  "datum": "YYYY-MM-DD oder unbekannt",
  "link": null,
  "gwoeScore": 0-10,
  "gwoeBegründung": "3-4 Sätze mit Bezug zu konkreten Themenfeldern",
  "gwoeMatrix": [
    { "field": "D4", "label": "Soziale öffentliche Leistung", "aspect": "Konkreter Bezug", "rating": 2, "symbol": "+" }
  ],
  "gwoeSchwerpunkt": ["D4", "D1"],
  "wahlprogrammScores": [
    { 
      "fraktion": "SPD",
      "istAntragsteller": true,
      "wahlprogramm": { 
        "score": 9, 
        "begründung": "...",
        "zitate": [
          {
            "text": "Exaktes Zitat aus Wahlprogramm",
            "quelle": "SPD NRW Wahlprogramm 2022, S. 47",
            "url": "/static/referenzen/spd-nrw-2022.pdf#page=47"
          }
        ]
      },
      "parteiprogramm": { "score": 8, "begründung": "..." }
    }
  ],
  "verbesserungen": [
    {
      "original": "Originaltext aus dem Antrag",
      "vorschlag": "Verbesserter Text mit **Ergänzungen** und ~~Streichungen~~",
      "begruendung": "Bezug zu GWÖ-Themenfeld"
    }
  ],
  "stärken": ["Punkt 1", "Punkt 2"],
  "schwächen": ["Punkt 1"],
  "empfehlung": "Ablehnen | Überarbeiten | Unterstützen mit Änderungen | Uneingeschränkt unterstützen",
  "empfehlungSymbol": "[X] | [!] | [+] | [++]",
  "verbesserungspotenzial": "gering | mittel | hoch | fundamental",
  "themen": ["Bildung", "Soziales"],
  "antragZusammenfassung": "1-2 Sätze Kernaussage",
  "antragKernpunkte": ["Punkt 1", "Punkt 2", "Punkt 3"]
}

## Wichtige Regeln

- **Verbesserungsvorschläge**: Maximal 3! Fokussiere auf die wirkungsvollsten Änderungen, die den GWÖ-Score am meisten verbessern würden.
- **Zitate**: Nur echte Textstellen aus den Wahlprogrammen verwenden (werden als Kontext mitgeliefert).
- **Matrix-Bewertung**: Bewerte nur Felder, die der Antrag tatsächlich berührt. Nicht jeder Antrag betrifft alle 25 Felder.
- **Gesamtscore-Berechnung**: Der gwoeScore (0-10) berücksichtigt die Matrix-Bewertungen:
  - Wenn EIN Feld -4 oder -5 hat → Gesamtscore maximal 3/10
  - Wenn EIN Feld -3 hat → Gesamtscore maximal 4/10
  - Bei "Ablehnen" → Score 0-2/10
  - Bei "Uneingeschränkt unterstützen" → Score 8-10/10
- **Matrix-Felder**: Bewertung -5 bis +5 (Symbole: −− / − / ○ / + / ++)"""


def get_bundesland_context(bundesland: str) -> str:
    """Get context for a specific state."""
    contexts = {
        "NRW": {
            "wahlprogramme": "wahlprogramme-nrw-2022.md",
            "parteiprogramme": "parteiprogramme.md",
            "regierungsfraktionen": ["CDU", "GRÜNE"],
        }
    }
    
    ctx = contexts.get(bundesland, contexts["NRW"])
    
    wahlprogramme = load_context_file(ctx["wahlprogramme"])
    parteiprogramme = load_context_file(ctx["parteiprogramme"])
    
    return f"""
## Wahlprogramme {bundesland} 2022

{wahlprogramme}

## Grundsatzprogramme der Parteien

{parteiprogramme}

## Regierungsfraktionen in {bundesland}

{', '.join(ctx['regierungsfraktionen'])}

Bei Oppositionsanträgen: Bewerte zusätzlich, ob die Regierungsfraktionen zustimmen würden.
"""


async def analyze_antrag(text: str, bundesland: str = "NRW", model: str = "qwen-plus") -> Assessment:
    """Analyze a parliamentary motion using the LLM."""
    
    client = AsyncOpenAI(
        api_key=settings.dashscope_api_key,
        base_url=settings.dashscope_base_url,
    )
    
    system_prompt = get_system_prompt()
    bundesland_context = get_bundesland_context(bundesland)
    
    # Extrahiere Fraktionen aus Text (einfache Heuristik)
    fraktionen = []
    for partei in WAHLPROGRAMME.keys():
        if partei in text or partei.lower() in text.lower():
            fraktionen.append(partei)
    
    # Suche relevante Zitate via semantische Suche (Embeddings)
    quotes_context = ""
    if EMBEDDINGS_DB.exists():
        try:
            semantic_quotes = get_relevant_quotes_for_antrag(text, fraktionen, top_k_per_partei=2)
            quotes_context = format_quotes_for_prompt(semantic_quotes)
        except Exception as e:
            print(f"Semantic search failed: {e}, falling back to keyword search")
            quotes = find_relevant_quotes(text, fraktionen)
            quotes_context = format_quote_for_prompt(quotes)
    else:
        # Fallback to keyword search
        quotes = find_relevant_quotes(text, fraktionen)
        quotes_context = format_quote_for_prompt(quotes)
    
    user_prompt = f"""Analysiere den folgenden Antrag:

<kontext>
{bundesland_context}
</kontext>

<wahlprogramm_zitate>
{quotes_context if quotes_context else "Keine relevanten Zitate gefunden."}
</wahlprogramm_zitate>

<antrag>
{text}
</antrag>

Bewerte nach GWÖ-Matrix 2.0 für Gemeinden:
1. GWÖ-Treue (0-10) mit Matrix-Zuordnung und Symbolen (++/+/○/−/−−)
2. Wahlprogrammtreue der einreichenden Fraktion(en) UND Regierungsfraktionen (0-10)
3. Parteiprogrammtreue der einreichenden Fraktion(en) UND Regierungsfraktionen (0-10)
4. Bis zu 3 Verbesserungsvorschläge in Redline-Syntax
5. Themen-Tags für Kategorisierung

Ausgabe als reines JSON ohne Markdown-Codeblöcke."""

    # Retry loop for JSON parsing errors
    max_retries = 3
    last_error = None
    
    for attempt in range(max_retries):
        response = await client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt},
            ],
            temperature=0.3 + (attempt * 0.1),  # Slightly increase temp on retry
            max_tokens=4000,
        )
        
        content = response.choices[0].message.content.strip()
        
        # Remove markdown code blocks if present
        if content.startswith("```"):
            content = content.split("\n", 1)[1]
        if content.endswith("```"):
            content = content.rsplit("```", 1)[0]
        if content.startswith("```json"):
            content = content[7:]
        content = content.strip()
        
        try:
            # Parse JSON
            data = json.loads(content)
            # Convert to Assessment model
            return Assessment.model_validate(data)
        except json.JSONDecodeError as e:
            last_error = e
            print(f"JSON parse error on attempt {attempt + 1}/{max_retries}: {e}")
            if attempt < max_retries - 1:
                print(f"Retrying with higher temperature...")
                continue
            else:
                # Log the problematic content for debugging
                print(f"Failed JSON content (first 500 chars): {content[:500]}")
                raise
