"""
Banner advanced legal checks (12-20).

12. Click count: reject vs. accept (CNIL enforcement)
13. Color contrast dark pattern (EDPB 3/2022)
14. Google Consent Mode default values
15. Consent before cookies (pre-banner tracking)
16. Registration-consent coupling (Art. 7(4) DSGVO)
17. Banner language vs. page language (all EU languages)
18. Consent cookie expiry > 13 months (CNIL)
19. Nudging/scrolling to find reject (EDPB 3/2022)
20. Emotional/manipulative language (EDPB 3/2022 Stirring)
"""

import logging
import re

from services.script_analyzer import Violation

logger = logging.getLogger(__name__)

# All EU/EEA official languages for Check 17
EU_LANGUAGES = {
    "bg": {"name": "Bulgarian", "cookie_words": ["бисквитки", "съгласие"]},
    "cs": {"name": "Czech", "cookie_words": ["cookies", "souhlas", "soubory cookie"]},
    "da": {"name": "Danish", "cookie_words": ["cookies", "samtykke", "cookiepolitik"]},
    "de": {"name": "German", "cookie_words": ["cookies", "einwilligung", "datenschutz", "zustimm"]},
    "el": {"name": "Greek", "cookie_words": ["cookies", "συναίνεση", "απορρήτου"]},
    "en": {"name": "English", "cookie_words": ["cookies", "consent", "privacy", "accept"]},
    "es": {"name": "Spanish", "cookie_words": ["cookies", "consentimiento", "privacidad", "aceptar"]},
    "et": {"name": "Estonian", "cookie_words": ["küpsised", "nõusolek", "privaatsus"]},
    "fi": {"name": "Finnish", "cookie_words": ["evästeet", "suostumus", "tietosuoja"]},
    "fr": {"name": "French", "cookie_words": ["cookies", "consentement", "confidentialité", "accepter"]},
    "ga": {"name": "Irish", "cookie_words": ["fianáin", "toiliú", "príobháideachais"]},
    "hr": {"name": "Croatian", "cookie_words": ["kolačići", "pristanak", "privatnost"]},
    "hu": {"name": "Hungarian", "cookie_words": ["sütik", "hozzájárulás", "adatvédel"]},
    "is": {"name": "Icelandic", "cookie_words": ["vafrakökur", "samþykki", "persónuvernd"]},
    "it": {"name": "Italian", "cookie_words": ["cookie", "consenso", "privacy", "accett"]},
    "lt": {"name": "Lithuanian", "cookie_words": ["slapukai", "sutikimas", "privatumas"]},
    "lv": {"name": "Latvian", "cookie_words": ["sīkdatnes", "piekrišana", "privātums"]},
    "mt": {"name": "Maltese", "cookie_words": ["cookies", "kunsens", "privatezza"]},
    "nl": {"name": "Dutch", "cookie_words": ["cookies", "toestemming", "privacy", "accepter"]},
    "no": {"name": "Norwegian", "cookie_words": ["informasjonskapsler", "samtykke", "personvern"]},
    "pl": {"name": "Polish", "cookie_words": ["ciasteczka", "zgoda", "prywatność", "akceptuj"]},
    "pt": {"name": "Portuguese", "cookie_words": ["cookies", "consentimento", "privacidade", "aceitar"]},
    "ro": {"name": "Romanian", "cookie_words": ["cookie-uri", "consimțământ", "confidențialitate"]},
    "sk": {"name": "Slovak", "cookie_words": ["cookies", "súhlas", "súkromie"]},
    "sl": {"name": "Slovenian", "cookie_words": ["piškotki", "soglasje", "zasebnost"]},
    "sv": {"name": "Swedish", "cookie_words": ["kakor", "samtycke", "integritet", "godkänn"]},
}


def _detect_language(text: str) -> str | None:
    """Detect language of text using cookie-specific keywords."""
    text_lower = text.lower()
    scores: dict[str, int] = {}
    for lang, info in EU_LANGUAGES.items():
        score = sum(1 for w in info["cookie_words"] if w in text_lower)
        if score > 0:
            scores[lang] = score
    if not scores:
        return None
    return max(scores, key=scores.get)  # type: ignore[arg-type]


def _detect_page_language(page_html: str, page_lang_attr: str) -> str | None:
    """Detect page language from <html lang=> or meta tags."""
    if page_lang_attr:
        return page_lang_attr[:2].lower()
    # Fallback: check meta content-language
    match = re.search(r'content=["\']([a-z]{2})', page_html[:2000].lower())
    return match.group(1) if match else None


async def run_advanced_checks(page, banner_text: str) -> list[Violation]:
    """Run checks 12-20 on the banner."""
    violations: list[Violation] = []
    banner_lower = banner_text.lower()

    # ── Check 12: Click count reject vs. accept ────────────────
    try:
        click_info = await page.evaluate("""
            () => {
                const banner = document.querySelector(
                    '#CybotCookiebotDialog, #onetrust-banner-sdk, #didomi-host, '
                    + '#usercentrics-root, .cky-consent-container, #cmpbox, '
                    + '[class*="cookie-banner"], [class*="consent-banner"], [role="dialog"]'
                );
                if (!banner) return null;
                const buttons = [...banner.querySelectorAll('button, a[role="button"], [class*="btn"]')];
                const acceptKw = ['akzeptieren','accept','zustimmen','agree','einverstanden','alle akzeptieren','accept all'];
                const rejectKw = ['ablehnen','reject','notwendige','decline','nur notwendige','reject all','alle ablehnen'];
                const settingsKw = ['einstellungen','settings','anpassen','customize','details','mehr'];
                let acceptClicks = 0, rejectClicks = 0;
                for (const b of buttons) {
                    const t = (b.textContent || '').trim().toLowerCase();
                    if (acceptKw.some(k => t.includes(k))) { acceptClicks = 1; }
                    if (rejectKw.some(k => t.includes(k))) { rejectClicks = 1; }
                }
                // If no direct reject but settings button exists → 2 clicks to reject
                if (rejectClicks === 0) {
                    for (const b of buttons) {
                        const t = (b.textContent || '').trim().toLowerCase();
                        if (settingsKw.some(k => t.includes(k))) { rejectClicks = 2; break; }
                    }
                }
                return { acceptClicks, rejectClicks };
            }
        """)
        if click_info and click_info["rejectClicks"] > click_info["acceptClicks"]:
            violations.append(Violation(
                service="Cookie-Banner",
                severity="HIGH",
                text=f"Ablehnung erfordert {click_info['rejectClicks']} Klick(s), "
                     f"Zustimmung nur {click_info['acceptClicks']}. "
                     f"Beides muss mit gleicher Anzahl Klicks erreichbar sein. "
                     f"Die CNIL hat hierfuer bereits Bussgelder verhaengt (Google: 150 Mio. EUR).",
                legal_ref="§25 TDDDG, CNIL Deliberation SAN-2021-024, EDPB Guidelines 05/2020",
            ))
    except Exception:
        pass

    # ── Check 13: Color contrast dark pattern ──────────────────
    try:
        color_info = await page.evaluate("""
            () => {
                const banner = document.querySelector(
                    '#CybotCookiebotDialog, #onetrust-banner-sdk, #didomi-host, '
                    + '#usercentrics-root, .cky-consent-container, #cmpbox, '
                    + '[class*="cookie-banner"], [class*="consent-banner"], [role="dialog"]'
                );
                if (!banner) return null;
                const bannerBg = window.getComputedStyle(banner).backgroundColor;
                const buttons = [...banner.querySelectorAll('button, a[role="button"], [class*="btn"]')];
                const acceptKw = ['akzeptieren','accept','zustimmen','agree','einverstanden'];
                const rejectKw = ['ablehnen','reject','notwendige','decline','nein'];
                let acceptColor = null, rejectColor = null;
                for (const b of buttons) {
                    const t = (b.textContent || '').trim().toLowerCase();
                    const style = window.getComputedStyle(b);
                    if (acceptKw.some(k => t.includes(k))) {
                        acceptColor = { bg: style.backgroundColor, color: style.color, border: style.borderColor };
                    }
                    if (rejectKw.some(k => t.includes(k))) {
                        rejectColor = { bg: style.backgroundColor, color: style.color, border: style.borderColor };
                    }
                }
                return { bannerBg, acceptColor, rejectColor };
            }
        """)
        if color_info and color_info.get("acceptColor") and color_info.get("rejectColor"):
            accept_bg = color_info["acceptColor"]["bg"]
            reject_bg = color_info["rejectColor"]["bg"]
            banner_bg = color_info["bannerBg"]
            # If reject button bg matches banner bg (invisible)
            if reject_bg == banner_bg or reject_bg in ("transparent", "rgba(0, 0, 0, 0)"):
                violations.append(Violation(
                    service="Cookie-Banner",
                    severity="MEDIUM",
                    text="Dark Pattern: 'Ablehnen'-Button hat gleiche Hintergrundfarbe wie der Banner "
                         "oder ist transparent — optisch kaum sichtbar. Beide Optionen muessen "
                         "visuell gleichwertig dargestellt werden.",
                    legal_ref="EDPB Guidelines 3/2022 (Deceptive Design Patterns), §25 TDDDG",
                ))
    except Exception:
        pass

    # ── Check 14: Google Consent Mode defaults ─────────────────
    try:
        gcm_info = await page.evaluate("""
            () => {
                const scripts = document.querySelectorAll('script');
                let foundDefault = false;
                let grantedBeforeConsent = false;
                for (const s of scripts) {
                    const text = s.textContent || '';
                    if (text.includes('consent') && text.includes('default')) {
                        foundDefault = true;
                        // Check for analytics_storage or ad_storage granted as default
                        const grantedMatch = text.match(/analytics_storage['"\\s:]*['"]granted/);
                        const adGranted = text.match(/ad_storage['"\\s:]*['"]granted/);
                        if (grantedMatch || adGranted) {
                            grantedBeforeConsent = true;
                        }
                    }
                }
                return { foundDefault, grantedBeforeConsent };
            }
        """)
        if gcm_info and gcm_info.get("grantedBeforeConsent"):
            violations.append(Violation(
                service="Google Consent Mode",
                severity="CRITICAL",
                text="Google Consent Mode: analytics_storage oder ad_storage ist als "
                     "Default auf 'granted' gesetzt BEVOR der Nutzer zugestimmt hat. "
                     "Default muss 'denied' sein bis explizite Einwilligung vorliegt.",
                legal_ref="§25 TDDDG, Art. 5(3) ePrivacy-RL, Google Consent Mode v2 Docs",
            ))
    except Exception:
        pass

    # ── Check 15: Cookies set before consent interaction ───────
    try:
        pre_consent_tracking = await page.evaluate("""
            () => {
                const cookies = document.cookie.split(';').map(c => c.trim().split('=')[0]);
                const trackingPatterns = ['_ga', '_gid', '_fbp', '_fbc', 'IDE', '_gcl', 'fr', '_pin',
                    '_tt_', 'li_sugr', '_hj', 'mp_', 'ajs_', '_clck', '_clsk'];
                return cookies.filter(name =>
                    trackingPatterns.some(p => name.startsWith(p))
                );
            }
        """)
        if pre_consent_tracking and len(pre_consent_tracking) > 0:
            violations.append(Violation(
                service="Cookie-Banner",
                severity="CRITICAL",
                text=f"Tracking-Cookies vor Consent gesetzt: {', '.join(pre_consent_tracking[:5])}. "
                     f"Nicht-essentielle Cookies duerfen erst NACH expliziter Einwilligung gesetzt werden.",
                legal_ref="§25 Abs. 1 TDDDG, Art. 5(3) ePrivacy-RL, EuGH C-673/17 Planet49",
            ))
    except Exception:
        pass

    # ── Check 16: Registration/Login consent coupling ──────────
    try:
        coupling_info = await page.evaluate("""
            () => {
                const forms = document.querySelectorAll('form');
                const results = [];
                for (const form of forms) {
                    const buttons = form.querySelectorAll('button[type="submit"], input[type="submit"]');
                    for (const btn of buttons) {
                        const text = (btn.textContent || btn.value || '').toLowerCase();
                        const isLoginRegister = /anmelden|registrieren|login|sign.?up|register|einloggen/.test(text);
                        if (!isLoginRegister) continue;
                        // Check surrounding text for consent coupling
                        const formText = form.textContent.toLowerCase();
                        const hasCoupling = /klicken.*stimmen.*zu|clicking.*agree|accept.*terms|akzeptieren.*bedingungen/.test(formText);
                        const hasCheckbox = form.querySelectorAll('input[type="checkbox"]').length;
                        if (hasCoupling && hasCheckbox === 0) {
                            results.push({ button: text.trim().substring(0, 30), hasCoupling: true });
                        }
                    }
                }
                return results;
            }
        """)
        if coupling_info and len(coupling_info) > 0:
            violations.append(Violation(
                service="Registration/Login",
                severity="HIGH",
                text=f"Koppelungsverbot: '{coupling_info[0]['button']}'-Button erteilt gleichzeitig "
                     f"Datenschutz-Einwilligung ohne separate Checkbox. Einwilligung darf nicht "
                     f"an Registrierung/Login gekoppelt werden.",
                legal_ref="Art. 7(4) DSGVO (Koppelungsverbot), ErwGr. 43, EuGH Planet49",
            ))
    except Exception:
        pass

    # ── Check 17: Banner language vs. page language ────────────
    try:
        page_lang = await page.evaluate("() => document.documentElement.lang || ''")
        page_lang_code = (page_lang or "")[:2].lower()
        banner_lang = _detect_language(banner_text)

        if page_lang_code and banner_lang and page_lang_code != banner_lang:
            page_lang_name = EU_LANGUAGES.get(page_lang_code, {}).get("name", page_lang_code)
            banner_lang_name = EU_LANGUAGES.get(banner_lang, {}).get("name", banner_lang)
            violations.append(Violation(
                service="Cookie-Banner",
                severity="MEDIUM",
                text=f"Banner-Sprache ({banner_lang_name}) stimmt nicht mit Seitensprache "
                     f"({page_lang_name}) ueberein. Der Cookie-Banner muss in der Sprache "
                     f"der Website verfasst sein, damit Nutzer eine informierte Entscheidung "
                     f"treffen koennen.",
                legal_ref="Art. 12(1) DSGVO (klare und einfache Sprache), ErwGr. 39",
            ))
    except Exception:
        pass

    # ── Check 18: Consent cookie expiry > 13 months ────────────
    try:
        consent_cookies = await page.evaluate("""
            () => {
                const consentNames = ['CookieConsent', 'cookieconsent_status', 'cc_cookie',
                    'eupubconsent', 'eupubconsent-v2', 'OptanonConsent', 'OptanonAlertBoxClosed',
                    'didomi_token', 'uc_settings', 'cky-consent', 'bp_consent',
                    'cmplz_consent_status', 'borlabs-cookie', 'cookie_notice_accepted'];
                const all = document.cookie.split(';').map(c => c.trim().split('=')[0]);
                return all.filter(name => consentNames.some(cn =>
                    name.toLowerCase().includes(cn.toLowerCase())
                ));
            }
        """)
        if consent_cookies:
            # Check expiry via cookie details (Playwright gives us this)
            cookies = await page.context.cookies()
            for cookie in cookies:
                name = cookie.get("name", "")
                if not any(cn.lower() in name.lower() for cn in [
                    "consent", "cookie", "optanon", "didomi", "uc_settings",
                    "cky-consent", "borlabs", "cmplz",
                ]):
                    continue
                expires = cookie.get("expires", 0)
                if expires > 0:
                    import time
                    days_until_expiry = (expires - time.time()) / 86400
                    if days_until_expiry > 395:  # 13 months ≈ 395 days
                        violations.append(Violation(
                            service="Cookie-Banner",
                            severity="MEDIUM",
                            text=f"Consent-Cookie '{name}' laeuft erst in {int(days_until_expiry)} Tagen ab. "
                                 f"Die CNIL empfiehlt maximal 13 Monate (395 Tage). "
                                 f"Danach muss erneut um Einwilligung gebeten werden.",
                            legal_ref="CNIL Leitlinien (max. 13 Monate), EDPB Guidelines 05/2020",
                        ))
                        break
    except Exception:
        pass

    # ── Check 19: Nudging — reject only visible after scrolling ─
    try:
        nudge_info = await page.evaluate("""
            () => {
                const banner = document.querySelector(
                    '#CybotCookiebotDialog, #onetrust-banner-sdk, #didomi-host, '
                    + '#usercentrics-root, .cky-consent-container, #cmpbox, '
                    + '[class*="cookie-banner"], [class*="consent-banner"], [role="dialog"]'
                );
                if (!banner) return null;
                const bannerRect = banner.getBoundingClientRect();
                const buttons = [...banner.querySelectorAll('button, a[role="button"], [class*="btn"]')];
                const rejectKw = ['ablehnen','reject','notwendige','decline','nein','alle ablehnen'];
                for (const b of buttons) {
                    const t = (b.textContent || '').trim().toLowerCase();
                    if (rejectKw.some(k => t.includes(k))) {
                        const btnRect = b.getBoundingClientRect();
                        // Button is below visible banner area
                        const isBelow = btnRect.top > bannerRect.bottom;
                        const isHidden = btnRect.height === 0 || btnRect.width === 0;
                        return { found: true, isBelow, isHidden, btnTop: btnRect.top, bannerBottom: bannerRect.bottom };
                    }
                }
                return { found: false };
            }
        """)
        if nudge_info and nudge_info.get("found"):
            if nudge_info.get("isBelow") or nudge_info.get("isHidden"):
                violations.append(Violation(
                    service="Cookie-Banner",
                    severity="HIGH",
                    text="Nudging: Der 'Ablehnen'-Button ist nicht im sichtbaren Bereich des Banners "
                         "— Nutzer muessen scrollen um ihn zu finden. Der Ablehn-Button muss ohne "
                         "Scrollen sichtbar sein.",
                    legal_ref="EDPB Guidelines 3/2022 (Deceptive Design: Hindering), §25 TDDDG",
                ))
    except Exception:
        pass

    # ── Check 20: Emotional/manipulative language (Stirring) ───
    stirring_patterns = [
        # German
        ("erleben sie das volle potenzial", "Stirring: 'Erleben Sie das volle Potenzial'"),
        ("bestmoegliches erlebnis", "Stirring: 'bestmoegliches Erlebnis'"),
        ("optimale nutzung", "Stirring: 'optimale Nutzung'"),
        ("eingeschraenkte funktionen", "Stirring: 'eingeschraenkte Funktionen' bei Ablehnung"),
        ("eingeschraenkt weiter", "Stirring: 'eingeschraenkt weiter'"),
        ("ohne cookies eingeschraenkt", "Stirring: 'ohne Cookies eingeschraenkt'"),
        ("volle funktionalitaet", "Stirring: 'volle Funktionalitaet'"),
        ("nur mit cookies moeglich", "Stirring: 'nur mit Cookies moeglich'"),
        # English
        ("best possible experience", "Stirring: 'best possible experience'"),
        ("full experience", "Stirring: 'full experience'"),
        ("limited functionality", "Stirring: 'limited functionality' if rejected"),
        ("enhanced experience", "Stirring: 'enhanced experience'"),
        ("may not work properly", "Stirring: 'may not work properly'"),
        ("some features may not", "Stirring: 'some features may not'"),
        # French
        ("meilleure experience", "Stirring: 'meilleure experience'"),
        ("experience optimale", "Stirring: 'experience optimale'"),
    ]
    for pattern, label in stirring_patterns:
        if pattern in banner_lower:
            # P67: konkrete Erklaerung statt nur Fachbegriff. Marketing/GF
            # versteht "Stirring" nicht — aber "Verlust-Framing" versteht
            # jeder, und der Vergleich (alt vs neutral) macht es greifbar.
            violations.append(Violation(
                service="Cookie-Banner",
                severity="LOW",
                text=f"Verlust-Framing im Banner-Text: {label}. "
                     f"Diese Formulierung suggeriert, dass das Nicht-Zustimmen "
                     f"eine schlechtere ('nicht-optimale') Nutzung bedeutet — "
                     f"selbst wenn die Website ohne Cookies technisch genauso "
                     f"funktioniert. Die EDPB (3/2022) nennt das 'Stirring': "
                     f"emotionale Hebel statt informierter Entscheidung. "
                     f"Empfehlung: neutrale Sprache ('Nutzung unserer Website') "
                     f"statt qualitativer Bewertung ('optimal', 'voll', "
                     f"'bestmoeglich').",
                legal_ref="EDPB Guidelines 3/2022 (Deceptive Design: Stirring), "
                          "Art. 7(4) DSGVO (Freiwilligkeit)",
            ))
            break  # One finding is enough

    return violations
