"""
Servicio para gestionar usuarios de Keycloak con validación de licencias
"""

import logging
from datetime import datetime
from typing import Any, Dict, List, Optional

from fastapi import HTTPException
from keycloak import KeycloakAdmin, KeycloakError
from sqlalchemy import and_, func
from sqlalchemy.orm import Session

from core.config import settings

from ..licenses.models import License, LicenseStatus
from ..user_tenant.models import UserTenant

logger = logging.getLogger(__name__)


def _normalize_keycloak_user(
    user_data: Dict[str, Any],
    role: Optional[str] = None,
    user_tenant: Optional[Any] = None,
) -> Dict[str, Any]:
    """
    Normaliza los datos de usuario de Keycloak al formato esperado por el DTO

    Keycloak usa camelCase, nuestro DTO usa snake_case
    """
    normalized = {
        "id": user_data.get("id"),
        "username": user_data.get("username", ""),
        "email": user_data.get("email", ""),
        "first_name": user_data.get("firstName", ""),
        "last_name": user_data.get("lastName", ""),
        "enabled": user_data.get("enabled", False),
        "email_verified": user_data.get("emailVerified", False),
        "created_timestamp": user_data.get("createdTimestamp"),
        "role": role,
    }

    # Agregar campos de perfil si user_tenant está disponible
    if user_tenant:
        normalized.update(
            {
                "avatar_url": user_tenant.avatar_url,
                "phone": user_tenant.phone,
                "bio": user_tenant.bio,
                "preferences": user_tenant.preferences or {},
            }
        )

    return normalized


class UserService:
    """Servicio para gestionar usuarios en Keycloak"""

    def __init__(self, db: Session, tenant_id: int, company_id: int = None):
        self.db = db
        self.tenant_id = tenant_id
        self.company_id = company_id

        # Inicializar cliente admin de Keycloak
        self.keycloak_admin = KeycloakAdmin(
            server_url=f"{settings.KEYCLOAK_SERVER_URL}/kcauth",
            username=settings.KEYCLOAK_ADMIN_USERNAME,
            password=settings.KEYCLOAK_ADMIN_PASSWORD,
            realm_name=settings.KEYCLOAK_REALM,
            verify=True,
        )

    def _get_license(self) -> License:
        """Obtiene la licencia del tenant actual"""
        license = (
            self.db.query(License).filter(License.tenant_id == self.tenant_id).first()
        )

        if not license:
            raise HTTPException(
                status_code=404, detail="License not found for this tenant"
            )

        if license.status != LicenseStatus.ACTIVE:
            raise HTTPException(
                status_code=403,
                detail=f"License is not active. Current status: {license.status.value}",
            )

        # Verificar si la licencia está vigente
        now = datetime.now(license.expires_at.tzinfo)
        if license.expires_at < now:
            raise HTTPException(status_code=403, detail="License has expired")

        return license

    def _check_user_limit(self) -> None:
        """Verifica si se puede crear un nuevo usuario según la licencia"""
        license = self._get_license()

        # Contar usuarios activos del tenant
        active_users = (
            self.db.query(func.count(UserTenant.id))
            .filter(
                and_(
                    UserTenant.tenant_id == self.tenant_id,
                    UserTenant.is_active == True,
                )
            )
            .scalar()
        )

        if active_users >= license.max_users:
            raise HTTPException(
                status_code=403,
                detail=f"User limit reached. Your license allows {license.max_users} users. "
                f"Currently active: {active_users}. Please upgrade your license.",
            )

    def create_user(
        self,
        email: str,
        username: str,
        first_name: str,
        last_name: str,
        password: str,
        role: Optional[str] = None,
        enabled: bool = True,
        email_verified: bool = False,
    ) -> Dict[str, Any]:
        """
        Crea un nuevo usuario en Keycloak y lo asocia al tenant

        Args:
            email: Email del usuario
            username: Nombre de usuario
            first_name: Nombre
            last_name: Apellido
            password: Contraseña inicial
            role: Rol en el tenant
            enabled: Si el usuario está habilitado
            email_verified: Si el email está verificado

        Returns:
            Información del usuario creado

        Raises:
            HTTPException: Si se alcanza el límite de usuarios o falla la creación
        """
        # Verificar límite de usuarios
        self._check_user_limit()

        try:
            # Crear usuario en Keycloak
            new_user = {
                "email": email,
                "username": username,
                "enabled": enabled,
                "emailVerified": email_verified,
                "firstName": first_name,
                "lastName": last_name,
                "attributes": {
                    "tenant_id": [
                        str(self.tenant_id)
                    ]  # Atributo requerido por Keycloak
                },
                "credentials": [
                    {
                        "type": "password",
                        "value": password,
                        "temporary": True,  # Usuario debe cambiar en primer login
                    }
                ],
            }

            user_id = self.keycloak_admin.create_user(new_user)
            logger.info(f"User created in Keycloak: {user_id}")

            # Obtener company_id si no se proporcionó
            if not self.company_id:
                # Obtener la primera company del tenant
                from api.v1.modules.a76.general_catalogs.company.models import Company

                company = (
                    self.db.query(Company)
                    .filter(Company.tenant_id == self.tenant_id)
                    .first()
                )

                if not company:
                    raise HTTPException(
                        status_code=400,
                        detail="No company found for this tenant. Please create a company first.",
                    )
                company_id = company.id
            else:
                company_id = self.company_id

            # Crear relación con el tenant
            user_tenant = UserTenant(
                keycloak_user_id=user_id,
                tenant_id=self.tenant_id,
                company_id=company_id,
                role=role,
                is_active=True,
            )
            self.db.add(user_tenant)
            self.db.commit()

            # Obtener información completa del usuario
            user_info = self.keycloak_admin.get_user(user_id)

            return _normalize_keycloak_user(user_info, role, user_tenant)

        except KeycloakError as e:
            logger.error(f"Keycloak error creating user: {str(e)}")
            self.db.rollback()

            # Manejar errores específicos
            if "User exists with same email" in str(e):
                raise HTTPException(
                    status_code=409, detail="A user with this email already exists"
                )
            elif "User exists with same username" in str(e):
                raise HTTPException(
                    status_code=409, detail="A user with this username already exists"
                )

            raise HTTPException(
                status_code=500, detail=f"Error creating user in Keycloak: {str(e)}"
            )
        except Exception as e:
            logger.error(f"Unexpected error creating user: {str(e)}")
            self.db.rollback()
            raise HTTPException(
                status_code=500, detail=f"Error creating user: {str(e)}"
            )

    def get_tenant_users(
        self, page: int = 1, page_size: int = 20, search: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Obtiene todos los usuarios del tenant con paginación

        Args:
            page: Número de página (1-indexed)
            page_size: Tamaño de página
            search: Término de búsqueda (busca en username, email, nombre, teléfono y bio)

        Returns:
            Dict con usuarios y metadatos de paginación
        """
        try:
            from ..permissions.models import UserCompanyRole, CompanyRole
            from sqlalchemy.orm import joinedload
            
            # Obtener relaciones usuario-tenant
            query = self.db.query(UserTenant).filter(
                and_(
                    UserTenant.tenant_id == self.tenant_id,
                    UserTenant.is_active == True,
                )
            )

            total = query.count()

            # Calcular offset
            offset = (page - 1) * page_size
            user_tenants = query.offset(offset).limit(page_size).all()

            # Obtener roles de usuarios en la compañía actual
            user_roles_query = self.db.query(UserCompanyRole).options(
                joinedload(UserCompanyRole.company_role)
            ).filter(
                and_(
                    UserCompanyRole.company_id == self.company_id,
                    UserCompanyRole.tenant_id == self.tenant_id,
                    UserCompanyRole.is_active == True
                )
            )
            
            # Crear un mapa de user_id -> lista de roles
            user_roles_map = {}
            for user_role in user_roles_query.all():
                if user_role.user_id not in user_roles_map:
                    user_roles_map[user_role.user_id] = []
                user_roles_map[user_role.user_id].append(user_role.company_role.name)

            # Obtener información de Keycloak para cada usuario
            users = []
            for ut in user_tenants:
                try:
                    user_info = self.keycloak_admin.get_user(ut.keycloak_user_id)
                    
                    # Obtener roles del usuario
                    roles = user_roles_map.get(ut.keycloak_user_id, [])
                    role_str = ", ".join(roles) if roles else None
                    
                    normalized_user = _normalize_keycloak_user(user_info, role_str, ut)

                    # Filtrar por búsqueda si se proporciona
                    if search:
                        search_lower = search.lower()
                        if (
                            search_lower in normalized_user.get("username", "").lower()
                            or search_lower in normalized_user.get("email", "").lower()
                            or search_lower
                            in normalized_user.get("first_name", "").lower()
                            or search_lower
                            in normalized_user.get("last_name", "").lower()
                            or search_lower
                            in normalized_user.get("phone", "").lower()
                            or search_lower
                            in normalized_user.get("bio", "").lower()
                        ):
                            users.append(normalized_user)
                    else:
                        users.append(normalized_user)

                except KeycloakError as e:
                    logger.warning(
                        f"Could not fetch user {ut.keycloak_user_id} from Keycloak: {str(e)}"
                    )
                    continue

            total_pages = (total + page_size - 1) // page_size

            return {
                "users": users,
                "total": len(users) if search else total,
                "page": page,
                "page_size": page_size,
                "total_pages": total_pages,
            }

        except Exception as e:
            logger.error(f"Error getting tenant users: {str(e)}")
            raise HTTPException(
                status_code=500, detail=f"Error getting users: {str(e)}"
            )

    def get_user(self, user_id: str) -> Dict[str, Any]:
        """Obtiene un usuario específico del tenant"""
        from ..permissions.models import UserCompanyRole
        from sqlalchemy.orm import joinedload
        
        # Verificar que el usuario pertenece al tenant
        user_tenant = (
            self.db.query(UserTenant)
            .filter(
                and_(
                    UserTenant.keycloak_user_id == user_id,
                    UserTenant.tenant_id == self.tenant_id,
                    UserTenant.is_active == True,
                )
            )
            .first()
        )

        if not user_tenant:
            raise HTTPException(status_code=404, detail="User not found in this tenant")

        # Obtener roles del usuario en la compañía actual
        user_roles = self.db.query(UserCompanyRole).options(
            joinedload(UserCompanyRole.company_role)
        ).filter(
            and_(
                UserCompanyRole.user_id == user_id,
                UserCompanyRole.company_id == self.company_id,
                UserCompanyRole.tenant_id == self.tenant_id,
                UserCompanyRole.is_active == True
            )
        ).all()
        
        roles = [ur.company_role.name for ur in user_roles]
        role_str = ", ".join(roles) if roles else None

        try:
            user_info = self.keycloak_admin.get_user(user_id)
            return _normalize_keycloak_user(user_info, role_str, user_tenant)
        except KeycloakError as e:
            logger.error(f"Error getting user from Keycloak: {str(e)}")
            raise HTTPException(status_code=404, detail="User not found in Keycloak")

    def update_user(
        self,
        user_id: str,
        first_name: Optional[str] = None,
        last_name: Optional[str] = None,
        email: Optional[str] = None,
        enabled: Optional[bool] = None,
        email_verified: Optional[bool] = None,
        role: Optional[str] = None,
        avatar_url: Optional[str] = None,
        phone: Optional[str] = None,
        bio: Optional[str] = None,
        preferences: Optional[dict] = None,
    ) -> Dict[str, Any]:
        """Actualiza información de un usuario"""
        # Verificar que el usuario pertenece al tenant
        user_tenant = (
            self.db.query(UserTenant)
            .filter(
                and_(
                    UserTenant.keycloak_user_id == user_id,
                    UserTenant.tenant_id == self.tenant_id,
                )
            )
            .first()
        )

        if not user_tenant:
            raise HTTPException(status_code=404, detail="User not found in this tenant")

        try:
            # Preparar datos de actualización para Keycloak
            update_data = {}
            if first_name is not None:
                update_data["firstName"] = first_name
            if last_name is not None:
                update_data["lastName"] = last_name
            if email is not None:
                update_data["email"] = email
            if enabled is not None:
                update_data["enabled"] = enabled
            if email_verified is not None:
                update_data["emailVerified"] = email_verified

            # Actualizar en Keycloak si hay cambios
            if update_data:
                self.keycloak_admin.update_user(user_id, update_data)

            # Actualizar campos en UserTenant
            if role is not None:
                user_tenant.role = role
            if avatar_url is not None:
                user_tenant.avatar_url = avatar_url
            if phone is not None:
                user_tenant.phone = phone
            if bio is not None:
                user_tenant.bio = bio
            if preferences is not None:
                user_tenant.preferences = preferences

            self.db.commit()
            self.db.refresh(user_tenant)

            # Obtener información actualizada
            user_info = self.keycloak_admin.get_user(user_id)

            return _normalize_keycloak_user(user_info, user_tenant.role, user_tenant)

        except KeycloakError as e:
            logger.error(f"Error updating user in Keycloak: {str(e)}")
            self.db.rollback()
            raise HTTPException(
                status_code=500, detail=f"Error updating user: {str(e)}"
            )

    def delete_user(self, user_id: str, soft_delete: bool = True) -> None:
        """
        Elimina un usuario del tenant

        Args:
            user_id: ID del usuario en Keycloak
            soft_delete: Si es True, solo desactiva. Si es False, elimina de Keycloak
        """
        # Verificar que el usuario pertenece al tenant
        user_tenant = (
            self.db.query(UserTenant)
            .filter(
                and_(
                    UserTenant.keycloak_user_id == user_id,
                    UserTenant.tenant_id == self.tenant_id,
                )
            )
            .first()
        )

        if not user_tenant:
            raise HTTPException(status_code=404, detail="User not found in this tenant")

        try:
            if soft_delete:
                # Solo desactivar la relación
                user_tenant.is_active = False
                self.db.commit()
            else:
                # Eliminar permanentemente de Keycloak
                self.keycloak_admin.delete_user(user_id)
                # Eliminar relación
                self.db.delete(user_tenant)
                self.db.commit()

        except KeycloakError as e:
            logger.error(f"Error deleting user from Keycloak: {str(e)}")
            self.db.rollback()
            raise HTTPException(
                status_code=500, detail=f"Error deleting user: {str(e)}"
            )

    def change_password(
        self, user_id: str, password: str, temporary: bool = True
    ) -> None:
        """Cambia la contraseña de un usuario"""
        # Verificar que el usuario pertenece al tenant
        user_tenant = (
            self.db.query(UserTenant)
            .filter(
                and_(
                    UserTenant.keycloak_user_id == user_id,
                    UserTenant.tenant_id == self.tenant_id,
                    UserTenant.is_active == True,
                )
            )
            .first()
        )

        if not user_tenant:
            raise HTTPException(status_code=404, detail="User not found in this tenant")

        try:
            self.keycloak_admin.set_user_password(
                user_id, password, temporary=temporary
            )
        except KeycloakError as e:
            logger.error(f"Error changing user password: {str(e)}")
            raise HTTPException(
                status_code=500, detail=f"Error changing password: {str(e)}"
            )

    def get_user_stats(self) -> Dict[str, Any]:
        """Obtiene estadísticas de usuarios del tenant"""
        license = self._get_license()

        # Contar usuarios activos e inactivos
        active_users = (
            self.db.query(func.count(UserTenant.id))
            .filter(
                and_(
                    UserTenant.tenant_id == self.tenant_id,
                    UserTenant.is_active == True,
                )
            )
            .scalar()
        )

        inactive_users = (
            self.db.query(func.count(UserTenant.id))
            .filter(
                and_(
                    UserTenant.tenant_id == self.tenant_id,
                    UserTenant.is_active == False,
                )
            )
            .scalar()
        )

        total_users = active_users + inactive_users
        users_available = max(0, license.max_users - active_users)
        usage_percentage = (
            (active_users / license.max_users * 100) if license.max_users > 0 else 0
        )

        return {
            "total_users": total_users,
            "active_users": active_users,
            "inactive_users": inactive_users,
            "max_users_allowed": license.max_users,
            "users_available": users_available,
            "usage_percentage": round(usage_percentage, 2),
        }

    def get_current_user_profile(self, keycloak_user_id: str) -> Dict[str, Any]:
        """
        Obtiene el perfil completo del usuario actual
        Combina datos de Keycloak con datos de UserTenant
        """
        user_tenant = (
            self.db.query(UserTenant)
            .filter(
                and_(
                    UserTenant.keycloak_user_id == keycloak_user_id,
                    UserTenant.is_active == True,
                )
            )
            .first()
        )

        if not user_tenant:
            raise HTTPException(status_code=404, detail="User profile not found")

        try:
            user_info = self.keycloak_admin.get_user(keycloak_user_id)
            return _normalize_keycloak_user(user_info, user_tenant.role, user_tenant)
        except KeycloakError as e:
            logger.error(f"Error getting user from Keycloak: {str(e)}")
            raise HTTPException(status_code=404, detail="User not found")

    def update_current_user_profile(
        self,
        keycloak_user_id: str,
        first_name: Optional[str] = None,
        last_name: Optional[str] = None,
        email: Optional[str] = None,
        avatar_url: Optional[str] = None,
        phone: Optional[str] = None,
        bio: Optional[str] = None,
        preferences: Optional[dict] = None,
    ) -> Dict[str, Any]:
        """
        Actualiza el perfil del usuario actual
        """
        user_tenant = (
            self.db.query(UserTenant)
            .filter(
                and_(
                    UserTenant.keycloak_user_id == keycloak_user_id,
                    UserTenant.is_active == True,
                )
            )
            .first()
        )

        if not user_tenant:
            raise HTTPException(status_code=404, detail="User profile not found")

        try:
            # Actualizar Keycloak
            update_data = {}
            if first_name is not None:
                update_data["firstName"] = first_name
            if last_name is not None:
                update_data["lastName"] = last_name
            if email is not None:
                update_data["email"] = email

            if update_data:
                self.keycloak_admin.update_user(keycloak_user_id, update_data)

            # Actualizar campos de perfil en UserTenant
            if avatar_url is not None:
                user_tenant.avatar_url = avatar_url
            if phone is not None:
                user_tenant.phone = phone
            if bio is not None:
                user_tenant.bio = bio
            if preferences is not None:
                user_tenant.preferences = preferences

            self.db.commit()
            self.db.refresh(user_tenant)

            # Retornar perfil actualizado
            user_info = self.keycloak_admin.get_user(keycloak_user_id)
            return _normalize_keycloak_user(user_info, user_tenant.role, user_tenant)

        except KeycloakError as e:
            logger.error(f"Error updating user profile: {str(e)}")
            self.db.rollback()
            raise HTTPException(
                status_code=500, detail=f"Error updating profile: {str(e)}"
            )
