import pyotp
import qrcode
import secrets
import hashlib
import json
import base64
import io
from typing import List, Optional, Dict, Tuple
from sqlalchemy.orm import Session
from ..models.user import User
import logging
logger = logging.getLogger(__name__)

class MFAService:

    @staticmethod
    def generate_secret() -> str:
        return pyotp.random_base32()

    @staticmethod
    def generate_qr_code(secret: str, email: str, app_name: str='Hotel Booking') -> str:
        totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(name=email, issuer_name=app_name)
        qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4)
        qr.add_data(totp_uri)
        qr.make(fit=True)
        img = qr.make_image(fill_color='black', back_color='white')
        buffer = io.BytesIO()
        img.save(buffer, format='PNG')
        img_data = base64.b64encode(buffer.getvalue()).decode()
        return f'data:image/png;base64,{img_data}'

    @staticmethod
    def generate_backup_codes(count: int=10) -> List[str]:
        codes = []
        for _ in range(count):
            code = secrets.token_urlsafe(6).upper()[:8]
            codes.append(code)
        return codes

    @staticmethod
    def hash_backup_code(code: str) -> str:
        return hashlib.sha256(code.encode()).hexdigest()

    @staticmethod
    def verify_backup_code(code: str, hashed_codes: List[str]) -> bool:
        code_hash = MFAService.hash_backup_code(code)
        return code_hash in hashed_codes

    @staticmethod
    def verify_totp(token: str, secret: str) -> bool:
        try:
            totp = pyotp.TOTP(secret)
            return totp.verify(token, valid_window=1)
        except Exception as e:
            logger.error(f'Error verifying TOTP: {str(e)}')
            return False

    @staticmethod
    def enable_mfa(db: Session, user_id: int, secret: str, verification_token: str) -> Tuple[bool, List[str]]:
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            raise ValueError('User not found')
        if not MFAService.verify_totp(verification_token, secret):
            raise ValueError('Invalid verification token')
        backup_codes = MFAService.generate_backup_codes()
        hashed_codes = [MFAService.hash_backup_code(code) for code in backup_codes]
        user.mfa_enabled = True
        user.mfa_secret = secret
        user.mfa_backup_codes = json.dumps(hashed_codes)
        db.commit()
        return (True, backup_codes)

    @staticmethod
    def disable_mfa(db: Session, user_id: int) -> bool:
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            raise ValueError('User not found')
        user.mfa_enabled = False
        user.mfa_secret = None
        user.mfa_backup_codes = None
        db.commit()
        return True

    @staticmethod
    def verify_mfa(db: Session, user_id: int, token: str, is_backup_code: bool=False) -> bool:
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            raise ValueError('User not found')
        if not user.mfa_enabled or not user.mfa_secret:
            raise ValueError('MFA is not enabled for this user')
        if is_backup_code:
            if not user.mfa_backup_codes:
                return False
            hashed_codes = json.loads(user.mfa_backup_codes)
            if not MFAService.verify_backup_code(token, hashed_codes):
                return False
            code_hash = MFAService.hash_backup_code(token)
            hashed_codes.remove(code_hash)
            user.mfa_backup_codes = json.dumps(hashed_codes) if hashed_codes else None
            db.commit()
            return True
        else:
            return MFAService.verify_totp(token, user.mfa_secret)

    @staticmethod
    def regenerate_backup_codes(db: Session, user_id: int) -> List[str]:
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            raise ValueError('User not found')
        if not user.mfa_enabled:
            raise ValueError('MFA is not enabled for this user')
        backup_codes = MFAService.generate_backup_codes()
        hashed_codes = [MFAService.hash_backup_code(code) for code in backup_codes]
        user.mfa_backup_codes = json.dumps(hashed_codes)
        db.commit()
        return backup_codes

    @staticmethod
    def get_mfa_status(db: Session, user_id: int) -> Dict:
        user = db.query(User).filter(User.id == user_id).first()
        if not user:
            raise ValueError('User not found')
        backup_codes_count = 0
        if user.mfa_backup_codes:
            backup_codes_count = len(json.loads(user.mfa_backup_codes))
        return {'mfa_enabled': user.mfa_enabled, 'backup_codes_count': backup_codes_count}
mfa_service = MFAService()