"""
ORY identity management integration with Admin API.
"""
import os
import logging
import asyncio
from typing import Optional, Dict, Any, List
from datetime import datetime
import httpx

from ..models.user_context import (
    AuthResult, UserContext, TokenClaims, UserRole
)
from .circuit_breaker import CircuitBreaker, CircuitBreakerOpen
from .rate_limiter import RateLimiter, RateLimitExceeded

logger = logging.getLogger(__name__)


class ORYAuthProvider:
    """ORY identity and session management with Admin API."""

    def __init__(
        self,
        admin_url: Optional[str] = None,
        public_url: Optional[str] = None,
        hydra_url: Optional[str] = None,
        http_client: Optional[httpx.AsyncClient] = None,
        enable_circuit_breaker: bool = True,
        enable_rate_limiting: bool = True
    ):
        """
        Initialize ORY provider.

        Args:
            admin_url: ORY Kratos Admin API URL
            public_url: ORY Kratos Public API URL
            hydra_url: ORY Hydra URL for OAuth 2.0
            http_client: Custom HTTP client for testing
            enable_circuit_breaker: Enable circuit breaker protection
            enable_rate_limiting: Enable rate limiting
        """
        self.admin_url = admin_url or os.getenv(
            'ORY_KRATOS_ADMIN_URL',
            'http://kratos:4434'
        )
        self.public_url = public_url or os.getenv(
            'ORY_KRATOS_PUBLIC_URL',
            'http://kratos:4433'
        )
        self.hydra_url = hydra_url or os.getenv(
            'ORY_HYDRA_URL',
            'http://hydra:4445'
        )

        # Admin API client with connection pooling
        if http_client:
            self.admin_client = http_client
        else:
            self.admin_client = httpx.AsyncClient(
                base_url=self.admin_url,
                timeout=30.0,
                limits=httpx.Limits(
                    max_keepalive_connections=20,
                    max_connections=100,
                    keepalive_expiry=30.0
                )
            )

        # Public API client
        self.public_client = httpx.AsyncClient(
            base_url=self.public_url,
            timeout=30.0
        )

        self.hydra_client = httpx.AsyncClient(
            base_url=self.hydra_url,
            timeout=30.0
        )

        # Circuit breaker for failure protection
        self.circuit_breaker = CircuitBreaker(
            failure_threshold=5,
            recovery_timeout=60,
            expected_exception=httpx.HTTPError,
            name="ORYAdminAPI"
        ) if enable_circuit_breaker else None

        # Rate limiter (100 requests per minute)
        self.rate_limiter = RateLimiter(
            max_requests=100,
            window_seconds=60,
            name="ORYAdminAPI"
        ) if enable_rate_limiting else None

    async def _request_with_retry(
        self,
        method: str,
        path: str,
        use_admin: bool = True,
        **kwargs
    ) -> Dict[str, Any]:
        """
        Make HTTP request with exponential backoff retry.

        Args:
            method: HTTP method
            path: Request path
            use_admin: Use admin client (vs public)
            **kwargs: Request arguments

        Returns:
            Response JSON

        Raises:
            httpx.HTTPError: If all retries fail
        """
        client = self.admin_client if use_admin else self.public_client

        for attempt in range(3):
            try:
                # Apply rate limiting
                if self.rate_limiter:
                    await self.rate_limiter.acquire()

                # Make request with circuit breaker
                async def make_request():
                    response = await client.request(method, path, **kwargs)
                    response.raise_for_status()
                    return response.json()

                if self.circuit_breaker:
                    return await self.circuit_breaker.call(make_request)
                else:
                    return await make_request()

            except httpx.HTTPStatusError as e:
                # Retry on 5xx errors
                if e.response.status_code >= 500 and attempt < 2:
                    wait_time = 2 ** attempt
                    logger.warning(
                        f"Request failed with {e.response.status_code}, "
                        f"retrying in {wait_time}s (attempt {attempt + 1}/3)"
                    )
                    await asyncio.sleep(wait_time)
                    continue
                raise

            except CircuitBreakerOpen:
                logger.error("Circuit breaker is open, rejecting request")
                raise

    async def create_identity(
        self,
        email: str,
        firm_id: str,
        role: str,
        **traits
    ) -> Optional[str]:
        """
        Create new identity via Admin API.

        Args:
            email: User email
            firm_id: Firm ID
            role: User role
            **traits: Additional identity traits

        Returns:
            Identity ID if successful
        """
        try:
            all_traits = {
                "email": email,
                "firm_id": firm_id,
                "role": role,
                **traits
            }

            data = await self._request_with_retry(
                "POST",
                "/admin/identities",
                json={
                    "schema_id": "default",
                    "traits": all_traits,
                    "metadata_public": {}
                }
            )

            identity_id = data.get("id")
            logger.info(f"Identity created: {identity_id} for {email}")
            return identity_id

        except Exception as e:
            logger.exception(f"Identity creation error: {e}")
            return None

    async def get_identity(self, identity_id: str) -> Optional[Dict[str, Any]]:
        """
        Get identity by ID.

        Args:
            identity_id: Identity ID

        Returns:
            Identity data
        """
        try:
            data = await self._request_with_retry(
                "GET",
                f"/admin/identities/{identity_id}"
            )
            return data

        except httpx.HTTPStatusError as e:
            if e.response.status_code == 404:
                logger.warning(f"Identity not found: {identity_id}")
                return None
            raise

        except Exception as e:
            logger.exception(f"Get identity error: {e}")
            return None

    async def list_identities(
        self,
        firm_id: Optional[str] = None,
        page_size: int = 100,
        page_token: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        List identities with pagination.

        Args:
            firm_id: Filter by firm ID
            page_size: Number of results per page
            page_token: Pagination token

        Returns:
            List of identities
        """
        try:
            params = {"page_size": page_size}
            if page_token:
                params["page_token"] = page_token

            data = await self._request_with_retry(
                "GET",
                "/admin/identities",
                params=params
            )

            identities = data if isinstance(data, list) else []

            # Filter by firm_id if specified
            if firm_id:
                identities = [
                    i for i in identities
                    if i.get("traits", {}).get("firm_id") == firm_id
                ]

            return identities

        except Exception as e:
            logger.exception(f"List identities error: {e}")
            return []

    async def update_identity(
        self,
        identity_id: str,
        traits: Dict[str, Any]
    ) -> Optional[Dict[str, Any]]:
        """
        Update identity traits.

        Args:
            identity_id: Identity ID
            traits: New traits

        Returns:
            Updated identity data
        """
        try:
            data = await self._request_with_retry(
                "PUT",
                f"/admin/identities/{identity_id}",
                json={
                    "schema_id": "default",
                    "traits": traits
                }
            )

            logger.info(f"Identity updated: {identity_id}")
            return data

        except Exception as e:
            logger.exception(f"Update identity error: {e}")
            return None

    async def delete_identity(self, identity_id: str) -> bool:
        """
        Delete identity (GDPR compliance).

        Args:
            identity_id: Identity ID

        Returns:
            True if successful
        """
        try:
            await self._request_with_retry(
                "DELETE",
                f"/admin/identities/{identity_id}"
            )

            logger.info(f"Identity deleted: {identity_id}")
            return True

        except Exception as e:
            logger.exception(f"Delete identity error: {e}")
            return False

    async def list_sessions(
        self,
        identity_id: str
    ) -> List[Dict[str, Any]]:
        """
        List all sessions for identity.

        Args:
            identity_id: Identity ID

        Returns:
            List of sessions
        """
        try:
            data = await self._request_with_retry(
                "GET",
                f"/admin/identities/{identity_id}/sessions"
            )

            return data if isinstance(data, list) else []

        except Exception as e:
            logger.exception(f"List sessions error: {e}")
            return []

    async def get_session_by_id(
        self,
        session_id: str
    ) -> Optional[Dict[str, Any]]:
        """
        Get session details by ID.

        Args:
            session_id: Session ID

        Returns:
            Session data
        """
        try:
            data = await self._request_with_retry(
                "GET",
                f"/admin/sessions/{session_id}"
            )
            return data

        except httpx.HTTPStatusError as e:
            if e.response.status_code == 404:
                return None
            raise

        except Exception as e:
            logger.exception(f"Get session error: {e}")
            return None

    async def revoke_session_by_id(self, session_id: str) -> bool:
        """
        Revoke session via Admin API.

        Args:
            session_id: Session ID

        Returns:
            True if successful
        """
        try:
            await self._request_with_retry(
                "DELETE",
                f"/admin/sessions/{session_id}"
            )

            logger.info(f"Session revoked: {session_id}")
            return True

        except Exception as e:
            logger.exception(f"Revoke session error: {e}")
            return False

    async def create_recovery_link(
        self,
        identity_id: str
    ) -> Optional[str]:
        """
        Create recovery link for password reset.

        Args:
            identity_id: Identity ID

        Returns:
            Recovery link URL
        """
        try:
            data = await self._request_with_retry(
                "POST",
                "/admin/recovery/link",
                json={"identity_id": identity_id}
            )

            recovery_link = data.get("recovery_link")
            logger.info(f"Recovery link created for {identity_id}")
            return recovery_link

        except Exception as e:
            logger.exception(f"Create recovery link error: {e}")
            return None

    async def create_verification_link(
        self,
        identity_id: str
    ) -> Optional[str]:
        """
        Create verification link for email verification.

        Args:
            identity_id: Identity ID

        Returns:
            Verification link URL
        """
        try:
            data = await self._request_with_retry(
                "POST",
                "/admin/verification/link",
                json={"identity_id": identity_id}
            )

            verification_link = data.get("verification_link")
            logger.info(f"Verification link created for {identity_id}")
            return verification_link

        except Exception as e:
            logger.exception(f"Create verification link error: {e}")
            return None

    async def get_session(self, session_token: str) -> Optional[UserContext]:
        """
        Validate session and return user context.

        Args:
            session_token: Session token

        Returns:
            UserContext if session is valid
        """
        try:
            response = await self.kratos_client.get(
                "/sessions/whoami",
                headers={"Authorization": f"Bearer {session_token}"}
            )

            if response.status_code != 200:
                return None

            data = response.json()
            identity = data.get('identity', {})
            traits = identity.get('traits', {})

            return UserContext(
                user_id=identity.get('id'),
                firm_id=traits.get('firm_id'),
                role=UserRole(traits.get('role', UserRole.CLIENT.value)),
                email=traits.get('email'),
                session_id=data.get('id'),
                metadata=identity.get('metadata_public', {})
            )

        except Exception as e:
            logger.exception(f"Session validation error: {e}")
            return None

    async def create_login_flow(self) -> Optional[Dict[str, Any]]:
        """
        Create login flow.

        Returns:
            Flow data including flow ID and UI
        """
        try:
            response = await self.kratos_client.get(
                "/self-service/login/api"
            )

            if response.status_code != 200:
                logger.error(f"Login flow creation failed: {response.text}")
                return None

            return response.json()

        except Exception as e:
            logger.exception(f"Login flow creation error: {e}")
            return None

    async def submit_login(
        self,
        flow_id: str,
        identifier: str,
        password: str
    ) -> Optional[AuthResult]:
        """
        Submit login credentials.

        Args:
            flow_id: Login flow ID
            identifier: Email/username
            password: Password

        Returns:
            AuthResult with session token
        """
        try:
            response = await self.kratos_client.post(
                f"/self-service/login?flow={flow_id}",
                json={
                    "method": "password",
                    "password_identifier": identifier,
                    "password": password
                }
            )

            if response.status_code != 200:
                logger.error(f"Login failed: {response.text}")
                return AuthResult(
                    success=False,
                    error="authentication_failed"
                )

            data = response.json()
            session_token = data.get('session_token')

            # Get user context from session
            user_context = await self.get_session(session_token)
            if not user_context:
                return AuthResult(
                    success=False,
                    error="session_creation_failed"
                )

            return AuthResult(
                success=True,
                user_context=user_context,
                access_token=session_token
            )

        except Exception as e:
            logger.exception(f"Login error: {e}")
            return AuthResult(
                success=False,
                error="internal_error",
                error_description=str(e)
            )

    async def revoke_session(self, session_token: str):
        """
        Revoke session (logout).

        Args:
            session_token: Session token to revoke
        """
        try:
            response = await self.kratos_client.delete(
                "/sessions",
                headers={"Authorization": f"Bearer {session_token}"}
            )

            if response.status_code == 204:
                logger.info("Session revoked successfully")
            else:
                logger.warning(f"Session revocation failed: {response.text}")

        except Exception as e:
            logger.exception(f"Session revocation error: {e}")

    async def create_oauth_client(
        self,
        name: str,
        redirect_uris: list,
        grant_types: Optional[list] = None
    ) -> Optional[Dict[str, Any]]:
        """
        Create OAuth 2.0 client in ORY Hydra.

        Args:
            name: Client name
            redirect_uris: Allowed redirect URIs
            grant_types: OAuth grant types

        Returns:
            Client data including client_id and client_secret
        """
        try:
            response = await self.hydra_client.post(
                "/admin/clients",
                json={
                    "client_name": name,
                    "redirect_uris": redirect_uris,
                    "grant_types": grant_types or [
                        "authorization_code",
                        "refresh_token"
                    ],
                    "response_types": ["code"],
                    "token_endpoint_auth_method": "client_secret_post"
                }
            )

            if response.status_code != 201:
                logger.error(f"OAuth client creation failed: {response.text}")
                return None

            data = response.json()
            logger.info(f"OAuth client created: {data['client_id']}")
            return data

        except Exception as e:
            logger.exception(f"OAuth client creation error: {e}")
            return None

    async def introspect_token(
        self,
        token: str
    ) -> Optional[Dict[str, Any]]:
        """
        Introspect OAuth token.

        Args:
            token: Access token

        Returns:
            Token metadata if active
        """
        try:
            response = await self.hydra_client.post(
                "/admin/oauth2/introspect",
                data={"token": token}
            )

            if response.status_code != 200:
                return None

            data = response.json()
            if not data.get('active'):
                return None

            return data

        except Exception as e:
            logger.exception(f"Token introspection error: {e}")
            return None

    async def close(self):
        """Close HTTP clients."""
        await self.kratos_client.aclose()
        await self.hydra_client.aclose()
