"""
Elephate CLI - Authentication module

Handles:
- Access code redemption
- Auth0 device flow (SSO)
- Token storage/retrieval
- API authentication
"""

import json
import os
import time
import webbrowser
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any, Dict, Optional, Tuple

import httpx
from platformdirs import user_config_dir

# Constants
APP_NAME = "elephate"
CONFIG_DIR = Path(user_config_dir(APP_NAME))
CREDENTIALS_FILE = CONFIG_DIR / "credentials.json"

# API configuration (can be overridden via env vars)
# Default to production API; developers can override with ELEPHATE_API_URL=http://localhost:8000
DEFAULT_API_URL = "https://elephate-backend-production.up.railway.app"
API_URL = os.getenv("ELEPHATE_API_URL", DEFAULT_API_URL)

# Auth0 configuration for device flow
AUTH0_DOMAIN = os.getenv("ELEPHATE_AUTH0_DOMAIN", "dev-oiz0zpb2ypakm6l7.us.auth0.com")
AUTH0_CLIENT_ID = os.getenv("ELEPHATE_AUTH0_CLIENT_ID", "ayOIhzNM5jRl3DxVx1s4Sg3HHPX4HPh1")
AUTH0_AUDIENCE = os.getenv("ELEPHATE_AUTH0_AUDIENCE", "https://api.elephate.ai")


@dataclass
class Credentials:
    """User credentials stored locally."""
    login_token: str
    email: Optional[str] = None
    user_id: Optional[int] = None
    auth_method: str = "access_code"  # "access_code" or "auth0"

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Credentials":
        return cls(
            login_token=data["login_token"],
            email=data.get("email"),
            user_id=data.get("user_id"),
            auth_method=data.get("auth_method", "access_code")
        )


def get_credentials() -> Optional[Credentials]:
    """Load stored credentials from disk."""
    if not CREDENTIALS_FILE.exists():
        return None

    try:
        with open(CREDENTIALS_FILE, "r", encoding="utf-8") as f:
            data = json.load(f)
            return Credentials.from_dict(data)
    except (json.JSONDecodeError, KeyError, IOError):
        return None


def save_credentials(credentials: Credentials) -> None:
    """Save credentials to disk."""
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)

    with open(CREDENTIALS_FILE, "w", encoding="utf-8") as f:
        json.dump(credentials.to_dict(), f, indent=2)

    # Set restrictive permissions on credentials file (Unix only)
    try:
        os.chmod(CREDENTIALS_FILE, 0o600)
    except (OSError, AttributeError):
        pass  # Windows doesn't support chmod the same way


def clear_credentials() -> bool:
    """Remove stored credentials. Returns True if file was deleted."""
    if CREDENTIALS_FILE.exists():
        CREDENTIALS_FILE.unlink()
        return True
    return False


def get_config_dir() -> Path:
    """Get the configuration directory path."""
    return CONFIG_DIR


# =============================================================================
# Access Code Authentication
# =============================================================================

def redeem_access_code(access_code: str) -> Tuple[bool, str, Optional[Credentials]]:
    """
    Redeem an access code and get a login token.

    Args:
        access_code: The access code to redeem

    Returns:
        Tuple of (success, message, credentials)
    """
    try:
        with httpx.Client(timeout=30.0) as client:
            response = client.post(
                f"{API_URL}/access-codes/redeem",
                json={"access_code": access_code}
            )

            if response.status_code == 200:
                data = response.json()
                credentials = Credentials(
                    login_token=data["login_token"],
                    auth_method="access_code"
                )
                # Fetch user info to get email
                user_info = verify_token(credentials.login_token)
                if user_info:
                    credentials.email = user_info.get("email")
                    credentials.user_id = user_info.get("id")

                return True, data.get("message", "Login geslaagd"), credentials

            elif response.status_code == 404:
                return False, "Ongeldige toegangscode", None

            elif response.status_code == 403:
                error_detail = response.json().get("detail", "Toegangscode niet meer geldig")
                return False, error_detail, None

            else:
                return False, f"Server error: {response.status_code}", None

    except httpx.ConnectError:
        return False, f"Kan geen verbinding maken met {API_URL}", None
    except httpx.TimeoutException:
        return False, "Verbinding timeout - probeer opnieuw", None
    except httpx.RequestError as e:
        return False, f"Verbindingsfout: {e}", None
    except Exception as e:
        return False, f"Onverwachte fout: {e}", None


# =============================================================================
# Auth0 Device Flow Authentication
# =============================================================================

def start_device_flow() -> Tuple[bool, str, Optional[Dict[str, Any]]]:
    """
    Start the Auth0 device authorization flow.

    Returns:
        Tuple of (success, message, device_code_response)
        device_code_response contains: device_code, user_code, verification_uri, etc.
    """
    if not AUTH0_CLIENT_ID:
        return False, (
            "Auth0 is niet geconfigureerd.\n"
            "  Set ELEPHATE_AUTH0_CLIENT_ID environment variable.\n"
            "  Of gebruik toegangscode login."
        ), None

    try:
        with httpx.Client(timeout=30.0) as client:
            response = client.post(
                f"https://{AUTH0_DOMAIN}/oauth/device/code",
                data={
                    "client_id": AUTH0_CLIENT_ID,
                    "scope": "openid profile email",
                    "audience": AUTH0_AUDIENCE
                }
            )

            if response.status_code == 200:
                data = response.json()
                return True, "Device flow gestart", data
            else:
                error_msg = response.json().get("error_description", "Kon device flow niet starten")
                return False, error_msg, None

    except httpx.RequestError as e:
        return False, f"Verbindingsfout met Auth0: {e}", None
    except Exception as e:
        return False, f"Onverwachte fout: {e}", None


def poll_device_flow(device_code: str, interval: int = 5, timeout: int = 300) -> Tuple[bool, str, Optional[Credentials]]:
    """
    Poll Auth0 for device flow completion.

    Args:
        device_code: The device code from start_device_flow
        interval: Polling interval in seconds
        timeout: Maximum time to wait in seconds

    Returns:
        Tuple of (success, message, credentials)
    """
    if not AUTH0_CLIENT_ID:
        return False, "Auth0 is niet geconfigureerd", None

    start_time = time.time()

    while time.time() - start_time < timeout:
        try:
            with httpx.Client(timeout=30.0) as client:
                response = client.post(
                    f"https://{AUTH0_DOMAIN}/oauth/token",
                    data={
                        "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
                        "device_code": device_code,
                        "client_id": AUTH0_CLIENT_ID
                    }
                )

                if response.status_code == 200:
                    data = response.json()
                    access_token = data.get("access_token")

                    if access_token:
                        credentials = Credentials(
                            login_token=access_token,
                            auth_method="auth0"
                        )
                        # Fetch user info
                        user_info = verify_token(access_token)
                        if user_info:
                            credentials.email = user_info.get("email")
                            credentials.user_id = user_info.get("id")

                        return True, "Login geslaagd via Auth0", credentials

                elif response.status_code == 403:
                    error = response.json().get("error", "")

                    if error == "authorization_pending":
                        # User hasn't completed authorization yet
                        time.sleep(interval)
                        continue
                    elif error == "slow_down":
                        # Too fast, increase interval
                        interval += 5
                        time.sleep(interval)
                        continue
                    elif error == "expired_token":
                        return False, "Verificatie verlopen. Probeer opnieuw.", None
                    elif error == "access_denied":
                        return False, "Toegang geweigerd door gebruiker", None
                    else:
                        return False, f"Auth0 fout: {error}", None
                else:
                    return False, f"Auth0 server error: {response.status_code}", None

        except httpx.RequestError as e:
            return False, f"Verbindingsfout: {e}", None
        except Exception as e:
            return False, f"Onverwachte fout: {e}", None

    return False, "Timeout - verificatie duurde te lang", None


def open_browser_for_auth(verification_uri: str) -> bool:
    """
    Open the browser for the user to complete authentication.

    Returns:
        True if browser was opened successfully
    """
    try:
        webbrowser.open(verification_uri)
        return True
    except Exception:
        return False


# =============================================================================
# Token Verification
# =============================================================================

def verify_token(token: str) -> Optional[Dict[str, Any]]:
    """
    Verify a token by calling /users/me.

    Args:
        token: The access token to verify

    Returns:
        User info dict if valid, None otherwise
    """
    try:
        with httpx.Client(timeout=10.0) as client:
            response = client.get(
                f"{API_URL}/users/me",
                headers={"Authorization": f"Bearer {token}"}
            )

            if response.status_code == 200:
                return response.json()

            return None

    except Exception:
        return None


def is_authenticated() -> bool:
    """Check if user has valid stored credentials."""
    credentials = get_credentials()
    if not credentials:
        return False

    # We have a token stored
    return bool(credentials.login_token)


def get_auth_header() -> Optional[Dict[str, str]]:
    """Get authorization header for API requests."""
    credentials = get_credentials()
    if not credentials:
        return None

    return {"Authorization": f"Bearer {credentials.login_token}"}


def get_api_url() -> str:
    """Get the API URL."""
    return API_URL
