import uuid
from datetime import datetime, timezone

from sqlalchemy.ext.asyncio import AsyncSession

from src.core.exceptions import ConflictException, UnauthorizedException, ValidationException
from src.core.logging import get_logger
from src.domain.entities import TechnicalUserEntity, UserRole
from src.infrastructure.database.repositories import TechnicalUserRepository
from src.services.auth.security import (
    RBACGuard,
    TokenData,
    create_access_token,
    create_refresh_token,
    decode_token,
    hash_password,
    validate_token_type,
    verify_password,
)

logger = get_logger("auth_service")


class AuthService:
    def __init__(self, session: AsyncSession) -> None:
        self._user_repo = TechnicalUserRepository(session)
        self._session = session

    async def register(self, username: str, password: str, email: str | None = None, full_name: str | None = None, role: UserRole = UserRole.VIEWER) -> TechnicalUserEntity:
        if not username or len(username) < 3:
            raise ValidationException("Username must be at least 3 characters")
        if not password or len(password) < 8:
            raise ValidationException("Password must be at least 8 characters")

        existing = await self._user_repo.get_by_username(username)
        if existing:
            raise ConflictException(f"Username '{username}' already exists")

        pw_hash = hash_password(password)
        entity = TechnicalUserEntity(
            username=username,
            email=email,
            full_name=full_name,
            role=role,
            is_active=True,
            scopes=self._default_scopes_for_role(role),
        )
        user = await self._user_repo.create(entity, pw_hash)
        logger.info("user_registered", extra={"username": username, "role": role.value})
        return user

    async def authenticate(self, username: str, password: str) -> tuple[TechnicalUserEntity, str, str]:
        user = await self._user_repo.get_by_username(username)
        if not user:
            raise UnauthorizedException("Invalid credentials")
        if not user.is_active:
            raise UnauthorizedException("Account is disabled")

        from src.infrastructure.database.models import TechnicalUserModel
        from sqlalchemy import select
        stmt = select(TechnicalUserModel.password_hash).where(TechnicalUserModel.id == user.id)
        result = await self._session.execute(stmt)
        pw_hash = result.scalar_one_or_none()
        if not pw_hash or not verify_password(password, pw_hash):
            raise UnauthorizedException("Invalid credentials")

        user.last_login_at = datetime.now(timezone.utc)
        await self._user_repo.update(user)

        access_token = create_access_token(subject=user.id, role=user.role, scopes=user.scopes)
        refresh_token = create_refresh_token(subject=user.id)

        logger.info("user_authenticated", extra={"username": username})
        return user, access_token, refresh_token

    async def refresh_access_token(self, refresh_token: str) -> tuple[str, str]:
        payload = decode_token(refresh_token)
        validate_token_type(payload, expected_type="refresh")

        user_id = uuid.UUID(payload["sub"])
        user = await self._user_repo.get_by_id(user_id)
        if not user or not user.is_active:
            raise UnauthorizedException("User not found or inactive")

        new_access = create_access_token(subject=user.id, role=user.role, scopes=user.scopes)
        new_refresh = create_refresh_token(subject=user.id)
        return new_access, new_refresh

    async def validate_access_token(self, token: str) -> TokenData:
        payload = decode_token(token)
        validate_token_type(payload, expected_type="access")
        return TokenData(payload)

    def _default_scopes_for_role(self, role: UserRole) -> list[str]:
        scopes_map: dict[UserRole, list[str]] = {
            UserRole.SUPERADMIN: ["*"],
            UserRole.ADMIN: ["pharmacies:read", "pharmacies:write", "medications:read", "medications:write", "inventory:read", "inventory:write", "connectors:read", "connectors:write", "sync:read", "sync:write", "reservations:read", "reservations:write", "audit:read", "users:read", "users:write", "api-keys:read", "api-keys:write"],
            UserRole.OPERATOR: ["pharmacies:read", "pharmacies:write", "medications:read", "inventory:read", "inventory:write", "connectors:read", "sync:read", "sync:write", "reservations:read", "reservations:write"],
            UserRole.VIEWER: ["pharmacies:read", "medications:read", "inventory:read", "reservations:read"],
            UserRole.API_CLIENT: ["pharmacies:read", "medications:read", "inventory:read", "reservations:read", "reservations:write"],
        }
        return scopes_map.get(role, ["pharmacies:read", "medications:read", "inventory:read"])
