import os
from datetime import datetime, timezone, timedelta
from typing import Optional, List

from jose import jwt
from pydantic import EmailStr
from sqlalchemy.orm import Session

from models import Users
from repositories.base_repository import BaseRepository
from schemas.auth import UserRead
from utils.constants import EmailVerificationStatus
from utils.encryption import bcrypt_context
from utils.exceptions import AlreadyExistsException, NotFoundException


class UserRepository(BaseRepository[UserRead, Users]):
    def __init__(self, db_session: Session):
        super().__init__(db_session, Users)

    def _to_entity(self, db_model: Optional[Users]) -> Optional[UserRead]:
        return UserRead.model_validate(db_model) if db_model else None

    def _to_db_model(self, entity: UserRead) -> Users:
        return Users(**entity.model_dump())

    async def create(self, user: UserRead) -> UserRead:
        existing_user = await self.get_by_email(user.email)
        if existing_user:
            raise AlreadyExistsException("User")
        return await super().create(user)

    async def get_by_email(
        self, email: EmailStr, convert_to_entity=True
    ) -> Optional[UserRead] | Optional[Users]:
        return await self.get_by_field(
            "email", email, convert_to_entity=convert_to_entity
        )

    async def authenticate(self, email: EmailStr, password: str):
        user = await self.get_by_email(email)
        if not user:
            return False
        if not bcrypt_context.verify(password, user.password):
            return False
        return user

    def create_access_token(
        self, name: str, user_id: int, email: str, expires_delta: timedelta
    ):
        encode = {
            "email": email,
            "id": user_id,
            "sub": name,
        }
        expires = datetime.now(timezone.utc) + expires_delta
        encode.update({"exp": expires})
        return jwt.encode(
            encode, os.getenv("SECRET_KEY"), algorithm=os.getenv("ALGORITHM")
        )

    async def update_step(self, user_id: int, step: int) -> UserRead:
        user = await self.get_by_id(user_id)
        if not user:
            raise NotFoundException("User")

        user.step = step
        user.updated_at = datetime.now(timezone.utc)
        return await self.update(user_id, user)

    async def update_default_company(self, user_id: int, company_id: int) -> UserRead:
        user = await self.get_by_id(user_id)
        if not user:
            raise NotFoundException("User")

        user.default_company = company_id
        user.updated_at = datetime.now(timezone.utc)
        return await self.update(user_id, user)

    async def verify_email(self, email: EmailStr) -> Optional[Users]:
        user = await self.get_by_email(email, convert_to_entity=False)
        if not user:
            return None

        user.is_email_verified = EmailVerificationStatus.VERIFIED.value
        user.email_verified_at = datetime.now(timezone.utc)
        self.db.commit()
        self.db.refresh(user)

        return user
