import time
import json
import base64
import urllib.request
import urllib.error

API_BASE_URL = "https://api.xplanung24.de"
#API_BASE_URL = "http://localhost:3004/dev"
_ALLOWED_SCHEMES = ("https://", "http://")


def _validate_url(url):
    """Ensure the URL uses an allowed scheme to prevent file:/ or custom scheme access."""
    if not any(url.startswith(scheme) for scheme in _ALLOWED_SCHEMES):
        raise ValueError(f"URL scheme not allowed: {url}")


class ProxyAuthenticationError(Exception):
    """Fehler wenn die Firewall/Proxy die Verbindung blockiert (HTTP 407)."""
    def __init__(self, api_url):
        self.api_url = api_url
        super().__init__(
            f"Die Verbindung zum Server wurde durch die Firewall/Proxy blockiert.\n\n"
            f"Bitte wenden Sie sich an Ihre IT-Abteilung und bitten Sie darum, "
            f"folgende Adresse in der Firewall freizuschalten:\n\n{api_url}"
        )


class CognitoSession:
    """
    Authentifizierungs-Session für QGIS Plugin.
    Kommuniziert mit der xplanung24 Public API statt direkt mit Cognito.
    """

    def __init__(self):
        self.id_token = None
        self.access_token = None
        self.refresh_token = None
        self.expiry = 0  # Unixzeit, wann AccessToken abläuft
        self.username = None
        self.password = None

        # Aus Token extrahierte Nutzerdaten
        self.company_id = None
        self.user_id = None
        self.user_name = None

    def login(self, username, password):
        """
        Authentifiziert den Benutzer über die Public API.
        Die API kommuniziert mit Cognito und gibt die Tokens zurück.
        """
        self.username = username
        self.password = password

        url = f"{API_BASE_URL}/auth/qgis/login"
        _validate_url(url)
        payload = json.dumps({
            "username": username,
            "password": password
        }).encode('utf-8')

        req = urllib.request.Request(
            url,
            data=payload,
            headers={
                'Content-Type': 'application/json'
            },
            method='POST'
        )

        try:
            with urllib.request.urlopen(req, timeout=30) as response:  # nosec B310 — URL is constructed from hardcoded HTTPS base
                data = json.loads(response.read().decode('utf-8'))

                if data.get('success'):
                    self._store_tokens(data)
                    self._extract_claims()
                else:
                    raise Exception(data.get('error', 'Authentifizierung fehlgeschlagen'))

        except urllib.error.HTTPError as e:
            if e.code == 407:
                raise ProxyAuthenticationError(API_BASE_URL) from e
            error_body = e.read().decode('utf-8')
            try:
                error_data = json.loads(error_body)
                error_msg = error_data.get('error', 'Authentifizierung fehlgeschlagen')
            except:
                error_msg = f"HTTP {e.code}: {error_body}"
            raise Exception(error_msg) from e

        except urllib.error.URLError as e:
            raise Exception(f"Verbindungsfehler: {e.reason}") from e

    def _store_tokens(self, result):
        """Speichert die Tokens aus der API-Antwort."""
        self.id_token = result.get('idToken')
        self.access_token = result.get('accessToken')
        if 'refreshToken' in result:
            self.refresh_token = result.get('refreshToken')

        # ExpiresIn ist in Sekunden, wir subtrahieren 60 Sekunden als Puffer
        expires_in = result.get('expiresIn', 3600)
        self.expiry = int(time.time()) + expires_in - 60

    def _extract_claims(self):
        """Extrahiert Claims aus dem ID Token (z.B. company_id)"""
        try:
            # Decode JWT token (middle part)
            token_parts = self.id_token.split('.')
            if len(token_parts) >= 2:
                # Add padding if needed
                payload = token_parts[1]
                padding = 4 - len(payload) % 4
                if padding != 4:
                    payload += '=' * padding

                decoded = base64.b64decode(payload)
                claims = json.loads(decoded)

                self.company_id = claims.get('custom:companyId') or claims.get('companyId') or 'default-company'
                self.user_id = claims.get('sub')
                self.user_name = claims.get('name') or claims.get('cognito:username') or claims.get('email') or ''

        except Exception:
            self.company_id = 'default-company'

    def ensure_valid_token(self):
        """Gibt immer ein gültiges IdToken zurück, erneuert falls nötig."""
        if time.time() < self.expiry:
            return self.id_token

        # Token abgelaufen -> Refresh über API
        self._refresh_token()
        return self.id_token

    def _refresh_token(self):
        """Erneuert die Tokens über die Public API."""
        if not self.refresh_token:
            raise Exception("Kein Refresh-Token vorhanden. Bitte erneut anmelden.")

        url = f"{API_BASE_URL}/auth/qgis/refresh"
        _validate_url(url)
        payload = json.dumps({
            "refreshToken": self.refresh_token
        }).encode('utf-8')

        req = urllib.request.Request(
            url,
            data=payload,
            headers={
                'Content-Type': 'application/json'
            },
            method='POST'
        )

        try:
            with urllib.request.urlopen(req, timeout=30) as response:  # nosec B310 — URL is constructed from hardcoded HTTPS base
                data = json.loads(response.read().decode('utf-8'))

                if data.get('success'):
                    self._store_tokens(data)
                else:
                    raise Exception(data.get('error', 'Token-Erneuerung fehlgeschlagen'))

        except urllib.error.HTTPError as e:
            if e.code == 407:
                raise ProxyAuthenticationError(API_BASE_URL) from e
            error_body = e.read().decode('utf-8')
            try:
                error_data = json.loads(error_body)
                error_msg = error_data.get('error', 'Token-Erneuerung fehlgeschlagen')
            except:
                error_msg = f"HTTP {e.code}: {error_body}"
            raise Exception(error_msg) from e

        except urllib.error.URLError as e:
            raise Exception(f"Verbindungsfehler: {e.reason}") from e

    def is_authenticated(self) -> bool:
        """True wenn ein Access-Token gesetzt ist."""
        return bool(self.access_token)

    def logout(self):
        """Löscht alle gespeicherten Tokens."""
        self.id_token = None
        self.access_token = None
        self.refresh_token = None
        self.expiry = 0
        self.company_id = None
        self.user_id = None
        self.user_name = None
