from datetime import datetime

from fastapi import HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from sqlalchemy.orm import selectinload

from src.auth.models import AuthLog
from src.auth.schemas import LoginRequest, CustomerResponse, UserResponse
from src.customer.models import Customer
from src.error import _ERROR
from src.security import verify_password, create_access_token
from src.user.models import User


class AuthService:
    @staticmethod
    async def login(data: LoginRequest, db: AsyncSession, ip: str) -> dict:
        account = await AuthService._get_account_by_login(data.login, db)

        if not account or not verify_password(data.password, account.password):
            _ERROR("AuthIncorrectCredentials")

        if isinstance(account, Customer):
            account_type = "customer"
            account_id = account.customer_id
            account_data = CustomerResponse.model_validate(account).model_dump()
        else:
            account_type = "user"
            account_id = account.user_id
            account_data = UserResponse.model_validate(account).model_dump()

        def serialize_for_jwt(obj):
            if isinstance(obj, dict):
                return {k: serialize_for_jwt(v) for k, v in obj.items()}
            if isinstance(obj, list):
                return [serialize_for_jwt(v) for v in obj]
            if isinstance(obj, datetime):
                return obj.isoformat()
            return obj

        token_payload = {
            "type": account_type,
            "account_data": serialize_for_jwt(account_data),
            "user_id": account_id
        }

        access_token = create_access_token(token_payload)

        db.add(AuthLog(login=data.login, ip=ip))
        await db.commit()

        return {
            "access_token": access_token,
            "token_type": "bearer",
            "account_data": account_data,
            "account_type": account_type
        }

    @staticmethod
    async def _get_account_by_login(login: str, db: AsyncSession):
        customer_result = await db.execute(select(Customer).where(Customer.phone == login))
        customer = customer_result.scalar_one_or_none()
        if customer:
            return customer

        user_result = await db.execute(
            select(User)
            .options(selectinload(User.role))
            .where(User.login == login)
        )
        return user_result.scalar_one_or_none()
