from fastapi import HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from core.security import hash_password, verify_password, create_access_token, create_refresh_token, decode_token
from repositories.user_repository import UserRepository
from repositories.refresh_token_repository import RefreshTokenRepository
from core.config import settings
from datetime import datetime, timezone, timedelta
import hashlib

class AuthService:
    def __init__(self, db: AsyncSession):
        self.db = db
        self.user_repo = UserRepository(db)
        self.refresh_repo = RefreshTokenRepository(db)

    async def register(self, email: str, password: str, name: str):
        existing = await self.user_repo.get_by_email(email)
        if existing:
            raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Email already registered")
        hashed = hash_password(password)
        user = await self.user_repo.create(email, hashed, name)
        return user

    async def login(self, email: str, password: str, user_agent: str = None, ip_address: str = None):
        user = await self.user_repo.get_by_email(email)
        if not user or not verify_password(password, user.password_hash):
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
        if not user.is_active:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Account inactive")
        access_token = create_access_token(user.id)
        refresh_token = create_refresh_token(user.id)
        # hash refresh token before storing
        token_hash = hashlib.sha256(refresh_token.encode()).hexdigest()
        expires_at = datetime.now(timezone.utc) + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
        await self.refresh_repo.create(user.id, token_hash, expires_at, user_agent, ip_address)
        return {"access_token": access_token, "refresh_token": refresh_token}

    async def refresh(self, refresh_token: str, user_agent: str = None, ip_address: str = None):
        payload = decode_token(refresh_token)
        if not payload or payload.get("type") != "refresh":
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token")
        user_id = payload.get("sub")
        if not user_id:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
        # verify token is not revoked
        token_hash = hashlib.sha256(refresh_token.encode()).hexdigest()
        stored = await self.refresh_repo.get_by_hash(token_hash)
        if not stored or stored.revoked_at is not None or stored.expires_at < datetime.now(timezone.utc):
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Refresh token invalid or expired")
        # revoke old token (rotation)
        await self.refresh_repo.revoke(stored.id)
        # issue new tokens
        new_access = create_access_token(user_id)
        new_refresh = create_refresh_token(user_id)
        new_hash = hashlib.sha256(new_refresh.encode()).hexdigest()
        expires_at = datetime.now(timezone.utc) + timedelta(days=settings.REFRESH_TOKEN_EXPIRE_DAYS)
        await self.refresh_repo.create(user_id, new_hash, expires_at, user_agent, ip_address)
        return {"access_token": new_access, "refresh_token": new_refresh}

    async def logout(self, refresh_token: str):
        token_hash = hashlib.sha256(refresh_token.encode()).hexdigest()
        stored = await self.refresh_repo.get_by_hash(token_hash)
        if stored:
            await self.refresh_repo.revoke(stored.id)

    async def logout_all(self, user_id: str):
        await self.refresh_repo.revoke_all_for_user(user_id)
