"""WaftAuth — the main entry point for waft_client.

Usage in a Lambda handler:

    from waft_client import WaftAuth

    auth = WaftAuth()

    def handler(event, context):
        response = auth.handle(event)
        if response:
            return response

        user = auth.get_user(event)
        if not user:
            return auth.login_redirect()

        return {"statusCode": 200, "body": f"Hello {user.email}!"}
"""

from __future__ import annotations

import json

from waft_client.config import AuthConfig
from waft_client.models import ProviderInfo, User
from waft_client.providers import apple, google, password
from waft_client.session import create_session_token, verify_token
from waft_client.store import AuthStore, SQLStore
from waft_client.ui import login_page, register_page


def _get_provider_infos(config: AuthConfig) -> list[ProviderInfo]:
    """Build the list of ProviderInfo for the enabled providers."""
    registry = {
        "google": google.INFO,
        "apple": apple.INFO,
        "password": password.INFO,
    }
    return [registry[p] for p in config.providers if p in registry]


class WaftAuth:
    """Zero-config authentication for waft-deployed apps.

    Handles login/register UI, password auth, OAuth via waft's centralized
    auth service, JWT sessions, and test mode for Playwright/BDD testing.
    """

    def __init__(
        self,
        config: AuthConfig | None = None,
        store: AuthStore | None = None,
    ) -> None:
        self.config = config or AuthConfig()
        self.store: AuthStore = store or SQLStore()

    def handle(self, event: dict) -> dict | None:
        """Process a Lambda event. Returns a response dict if this was an auth
        route, None if the app should handle it."""
        path = event.get("rawPath", "/")
        method = (
            event.get("requestContext", {}).get("http", {}).get("method", "GET").upper()
        )

        # Dev login — instant login without password for local development
        if self.config.dev_login and path == "/auth/dev-login" and method == "POST":
            return self._dev_login()

        # Auth routes
        if path == self.config.login_path and method == "GET":
            return self._login_page()
        if path == self.config.register_path and method == "GET":
            return self._register_page()
        if path == self.config.logout_path:
            return self._logout()
        if path == "/auth/providers" and method == "GET":
            return self._providers_json()
        if path == "/auth/me" and method == "GET":
            return self._me(event)
        if path == self.config.callback_path and method == "GET":
            return self._handle_oauth_callback(event)

        # Delegate to providers
        for provider_id in self.config.providers:
            handler = _PROVIDER_HANDLERS.get(provider_id)
            if handler:
                result = handler(path, method, event, self.store, self.config)
                if result is not None:
                    return result

        return None

    def get_user(self, event: dict) -> User | None:
        """Get the authenticated user from the request, or None."""
        # Test mode bypass
        if self.config.test_mode:
            test_auth = _get_header(event, "x-waft-test-auth")
            if test_auth:
                from datetime import datetime, timezone
                data = json.loads(test_auth)
                return User(
                    id=data.get("user_id", "test-user"),
                    email=data.get("email", "test@test.com"),
                    created_at=datetime.now(timezone.utc),
                )

        # Check JWT cookie
        token = _get_cookie(event, self.config.cookie_name)
        if not token:
            return None

        payload = verify_token(token, self.config.jwt_secret)
        if not payload:
            return None

        return self.store.get_user(payload["sub"])

    def get_providers(self) -> list[ProviderInfo]:
        """Get the list of enabled authentication providers."""
        return _get_provider_infos(self.config)

    def login_redirect(self) -> dict:
        """Return a 302 redirect to the login page."""
        return {
            "statusCode": 302,
            "headers": {"location": self.config.login_path},
            "body": "",
        }

    def invite_user(self, email: str) -> str:
        """Invite a user (for invite_only mode). Returns the invite token."""
        return self.store.create_invite(email)

    # --- Private methods ---

    def _login_page(self) -> dict:
        providers = _get_provider_infos(self.config)
        html = login_page(
            providers,
            self.config.register_path,
            signup_mode=self.config.signup_mode,
            dev_login=self.config.dev_login,
        )
        return {"statusCode": 200, "headers": {"content-type": "text/html"}, "body": html}

    def _register_page(self) -> dict:
        if self.config.signup_mode == "invite_only":
            return {
                "statusCode": 200,
                "headers": {"content-type": "text/html"},
                "body": "<h1>Signup requires an invitation</h1>",
            }
        providers = _get_provider_infos(self.config)
        html = register_page(providers, self.config.login_path)
        return {"statusCode": 200, "headers": {"content-type": "text/html"}, "body": html}

    def _logout(self) -> dict:
        # Clear cookie by setting max-age=0
        parts = [
            f"{self.config.cookie_name}=",
            "Path=/",
            "HttpOnly",
            "SameSite=Lax",
            "Max-Age=0",
        ]
        if self.config.cookie_secure:
            parts.append("Secure")
        return {
            "statusCode": 302,
            "headers": {"location": self.config.after_logout_path},
            "cookies": ["; ".join(parts)],
            "body": "",
        }

    def _providers_json(self) -> dict:
        providers = [
            {"id": p.id, "name": p.name, "type": p.provider_type}
            for p in _get_provider_infos(self.config)
        ]
        return {
            "statusCode": 200,
            "headers": {"content-type": "application/json"},
            "body": json.dumps(providers),
        }

    def _me(self, event: dict) -> dict:
        user = self.get_user(event)
        if not user:
            return {
                "statusCode": 401,
                "headers": {"content-type": "application/json"},
                "body": json.dumps({"error": "Not authenticated"}),
            }
        return {
            "statusCode": 200,
            "headers": {"content-type": "application/json"},
            "body": json.dumps(user.to_dict()),
        }

    def _handle_oauth_callback(self, event: dict) -> dict:
        """Handle OAuth callback from the waft auth service.

        The auth service redirects here with ?token=<jwt> containing:
        {provider, email, external_id, app_id, exp}
        signed with the app_secret.
        """
        params = event.get("queryStringParameters") or {}
        token = params.get("token", "")

        if not token or not self.config.app_secret:
            return self._error_page("OAuth callback failed: missing token or app secret")

        payload = verify_token(token, self.config.app_secret)
        if not payload:
            return self._error_page("OAuth callback failed: invalid or expired token")

        provider_name = payload.get("provider", "")
        email = payload.get("email", "")
        external_id = payload.get("external_id", "")

        if not email or not external_id:
            return self._error_page("OAuth callback failed: missing user info")

        # Check invite_only mode
        if self.config.signup_mode == "invite_only":
            existing = self.store.get_user_by_provider(provider_name, external_id)
            if not existing and not self.store.has_invite(email):
                return self._error_page("Signup requires an invitation")

        # Find or create user
        user = self.store.get_user_by_provider(provider_name, external_id)
        if not user:
            # Check if user exists with this email (link accounts)
            user = self.store.get_user_by_email(email)
            if not user:
                user = self.store.create_user(email)
            self.store.store_credential(user.id, provider_name, external_id, "")

            if self.config.signup_mode == "invite_only":
                self.store.use_invite(email)

        session_token = create_session_token(
            user.id, user.email, self.config.jwt_secret, self.config.cookie_max_age
        )
        return _redirect_with_cookie(
            self.config.after_login_path, self.config, session_token
        )

    def _dev_login(self) -> dict:
        """Instant dev login — creates/finds dev@waft.dev and issues session."""
        email = "dev@waft.dev"
        user = self.store.get_user_by_email(email)
        if not user:
            user = self.store.create_user(email)
        session_token = create_session_token(
            user.id, user.email, self.config.jwt_secret, self.config.cookie_max_age
        )
        return _redirect_with_cookie(
            self.config.after_login_path, self.config, session_token
        )

    def _error_page(self, msg: str) -> dict:
        return {
            "statusCode": 200,
            "headers": {"content-type": "text/html"},
            "body": f'<h1>Authentication Error</h1><p>{msg}</p><a href="{self.config.login_path}">Try again</a>',
        }


# Provider route handlers
_PROVIDER_HANDLERS = {
    "password": password.handle_route,
    "google": google.handle_route,
    "apple": apple.handle_route,
}


# --- Helpers ---


def _get_header(event: dict, name: str) -> str | None:
    """Get a header value (case-insensitive) from a Lambda event."""
    headers = event.get("headers", {})
    return headers.get(name) or headers.get(name.title())


def _get_cookie(event: dict, name: str) -> str | None:
    """Get a cookie value from a Lambda v2 event."""
    # Lambda v2 has cookies as a separate list
    cookies = event.get("cookies", [])
    for c in cookies:
        parts = c.split("=", 1)
        if len(parts) == 2 and parts[0].strip() == name:
            return parts[1].strip()
    # Fallback: parse from Cookie header
    cookie_header = _get_header(event, "cookie")
    if cookie_header:
        for c in cookie_header.split(";"):
            parts = c.strip().split("=", 1)
            if len(parts) == 2 and parts[0].strip() == name:
                return parts[1].strip()
    return None


def _redirect_with_cookie(path: str, config: AuthConfig, token: str) -> dict:
    parts = [
        f"{config.cookie_name}={token}",
        "Path=/",
        "HttpOnly",
        "SameSite=Lax",
        f"Max-Age={config.cookie_max_age}",
    ]
    if config.cookie_secure:
        parts.append("Secure")
    return {
        "statusCode": 302,
        "headers": {"location": path},
        "cookies": ["; ".join(parts)],
        "body": "",
    }
