import hashlib
from datetime import datetime, timedelta, timezone
from typing import Optional
from uuid import UUID

from sqlalchemy import select, and_
from sqlalchemy.ext.asyncio import AsyncSession

from src.apps.auth.models.user import User
from src.apps.auth.models.refresh_token import RefreshToken
from src.core.exceptions import UnauthorizedError, NotFoundError
from src.core.security import (
    hash_password,
    verify_password,
    create_access_token,
    create_refresh_token,
    decode_refresh_token,
    build_token_payload,
)
from src.core.config import settings


def _hash_token(token: str) -> str:
    return hashlib.sha256(token.encode()).hexdigest()


class AuthService:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def get_user_by_id(self, user_id: UUID) -> Optional[User]:
        result = await self.db.execute(select(User).where(User.id == user_id))
        return result.scalar_one_or_none()

    async def get_user_by_email(self, email: str, tenant_id: Optional[str] = None) -> Optional[User]:
        conditions = [User.email == email.lower(), User.deleted_at.is_(None)]
        if tenant_id:
            conditions.append(User.tenant_id == UUID(tenant_id))
        result = await self.db.execute(select(User).where(and_(*conditions)))
        return result.scalar_one_or_none()

    async def login(self, email: str, password: str, tenant_id: Optional[str] = None):
        user = await self.get_user_by_email(email, tenant_id)
        if not user or not verify_password(password, user.password_hash):
            raise UnauthorizedError("Invalid email or password")
        if user.status == "inactive":
            raise UnauthorizedError("Account is deactivated")

        # Update last login
        user.last_login_at = datetime.now(timezone.utc)
        user.status = "active"

        # Generate tokens
        payload = build_token_payload(user)
        access_token = create_access_token(payload)
        refresh_token = create_refresh_token(payload)

        # Store refresh token
        token_record = RefreshToken(
            user_id=user.id,
            token_hash=_hash_token(refresh_token),
            expires_at=datetime.now(timezone.utc)
            + timedelta(days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS),
        )
        self.db.add(token_record)
        await self.db.flush()

        return access_token, refresh_token, user

    async def refresh_access_token(self, refresh_token: str) -> str:
        payload = decode_refresh_token(refresh_token)
        if not payload:
            raise UnauthorizedError("Invalid or expired refresh token")

        token_hash = _hash_token(refresh_token)
        result = await self.db.execute(
            select(RefreshToken).where(
                and_(
                    RefreshToken.token_hash == token_hash,
                    RefreshToken.revoked_at.is_(None),
                )
            )
        )
        record = result.scalar_one_or_none()
        if not record or record.is_expired:
            raise UnauthorizedError("Refresh token is invalid or expired")

        user = await self.get_user_by_id(record.user_id)
        if not user:
            raise UnauthorizedError("User not found")

        token_payload = build_token_payload(user)
        return create_access_token(token_payload)

    async def logout(self, refresh_token: str):
        token_hash = _hash_token(refresh_token)
        result = await self.db.execute(
            select(RefreshToken).where(RefreshToken.token_hash == token_hash)
        )
        record = result.scalar_one_or_none()
        if record:
            record.revoked_at = datetime.now(timezone.utc)
            await self.db.flush()
