"""
Authentication client for Daisi SDK.
"""

from typing import Optional

import grpc

from protos.v1 import Auth_pb2, Auth_pb2_grpc
from protos.v1.models import AuthModels_pb2

from ..base_client import BaseClient
from ..config import DaisiConfig
from ..exceptions import AuthenticationError, DaisiError


class AuthClient(BaseClient):
    """Client for authentication operations."""

    def __init__(self, config: Optional[DaisiConfig] = None) -> None:
        """Initialize auth client.

        Args:
            config: Daisi configuration.
        """
        super().__init__(config)
        self._stub: Optional[Auth_pb2_grpc.AuthProtoStub] = None

    @property
    def stub(self) -> Auth_pb2_grpc.AuthProtoStub:
        """Get or create the gRPC stub."""
        if self._stub is None:
            self._stub = Auth_pb2_grpc.AuthProtoStub(self._get_channel())
        return self._stub

    def create_client_key(
        self,
        secret_key: str,
        owner_id: Optional[str] = None,
        owner_name: Optional[str] = None,
        owner_role: Optional[AuthModels_pb2.SystemRoles] = None,
        access_to_ids: Optional[list[str]] = None,
    ) -> tuple[str, str]:
        """Create a new client key.

        Args:
            secret_key: Secret key for authentication.
            owner_id: Optional ID of the key owner.
            owner_name: Optional name of the key owner.
            owner_role: Optional role of the key owner.
            access_to_ids: Optional list of resource IDs this key can access.

        Returns:
            Tuple of (client_key, owner_id).

        Raises:
            AuthenticationError: If key creation fails.
            DaisiError: If request fails.
        """
        request = AuthModels_pb2.CreateClientKeyRequest(SecretKey=secret_key)
        if owner_id:
            request.OwnerId = owner_id
        if owner_name:
            request.OwnerName = owner_name
        if owner_role is not None:
            request.OwnerRole = owner_role
        if access_to_ids:
            request.AccessToIds.extend(access_to_ids)

        try:
            response = self.stub.CreateClientKey(request, metadata=self._get_metadata())
            return response.ClientKey, response.OwnerId
        except grpc.RpcError as e:
            raise AuthenticationError(
                f"Failed to create client key: {e.details()}"
            ) from e

    def validate_client_key(self, secret_key: str, client_key: str) -> bool:
        """Validate a client key.

        Args:
            secret_key: Secret key for validation.
            client_key: Client key to validate.

        Returns:
            True if valid, False otherwise.

        Raises:
            DaisiError: If request fails.
        """
        request = AuthModels_pb2.ValidateClientKeyRequest(
            SecretKey=secret_key, ClientKey=client_key
        )

        try:
            response = self.stub.ValidateClientKey(
                request, metadata=self._get_metadata()
            )
            return response.IsValid
        except grpc.RpcError as e:
            raise DaisiError(f"Failed to validate client key: {e.details()}") from e

    def send_auth_code(self, email_or_phone: str) -> bool:
        """Send authentication code to email or phone.

        Args:
            email_or_phone: Email address or phone number.

        Returns:
            True if code sent successfully.

        Raises:
            DaisiError: If request fails.
        """
        request = AuthModels_pb2.SendAuthCodeRequest(EmailOrPhone=email_or_phone)

        try:
            response = self.stub.SendAuthCode(request, metadata=self._get_metadata())
            if not response.Success:
                raise DaisiError(f"Failed to send auth code: {response.ErrorMessage}")
            return response.Success
        except grpc.RpcError as e:
            raise DaisiError(f"Failed to send auth code: {e.details()}") from e

    def validate_auth_code(
        self,
        secret_key: str,
        email_or_phone: str,
        auth_code: str,
        app_id: str,
    ) -> tuple[str, str, str, str]:
        """Validate authentication code and get client key.

        Args:
            secret_key: Secret key for validation.
            email_or_phone: Email or phone that received the code.
            auth_code: The authentication code.
            app_id: Application ID.

        Returns:
            Tuple of (client_key, user_name, account_name, account_id).

        Raises:
            AuthenticationError: If validation fails.
            DaisiError: If request fails.
        """
        request = AuthModels_pb2.ValidateAuthCodeRequest(
            SecretKey=secret_key,
            EmailOrPhone=email_or_phone,
            AuthCode=auth_code,
            AppId=app_id,
        )

        try:
            response = self.stub.ValidateAuthCode(
                request, metadata=self._get_metadata()
            )
            if not response.Success:
                raise AuthenticationError(
                    f"Auth code validation failed: {response.ErrorMessage}"
                )
            return (
                response.ClientKey,
                response.UserName,
                response.AccountName,
                response.AccountId,
            )
        except grpc.RpcError as e:
            raise AuthenticationError(
                f"Failed to validate auth code: {e.details()}"
            ) from e

    def get_authenticated_user(self) -> AuthModels_pb2.GetAuthenticatedUserResponse:
        """Get the currently authenticated user.

        Returns:
            Response containing the authenticated User object.

        Raises:
            AuthenticationError: If not authenticated.
            DaisiError: If request fails.
        """
        request = AuthModels_pb2.GetAuthenticatedUserRequest()

        try:
            return self.stub.GetAuthenticatedUser(
                request, metadata=self._get_metadata()
            )
        except grpc.RpcError as e:
            raise AuthenticationError(
                f"Failed to get authenticated user: {e.details()}"
            ) from e
