from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, Any
from jose import JWTError, jwt
import hashlib
from passlib.context import CryptContext

from genovation_advanced_auth.core.config import AuthConfig, get_config
from genovation_advanced_auth.exceptions import TokenExpiredError, InvalidTokenError

# Password hashing context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def create_access_token(
    data: Dict[str, Any],
    secret_key: Optional[str] = None,
    algorithm: Optional[str] = None,
    expires_minutes: Optional[int] = None,
    config: Optional[AuthConfig] = None
) -> str:
    """
    Create JWT access token.
    
    Args:
        data: Data to encode in token
        secret_key: JWT secret key (if None, uses config)
        algorithm: JWT algorithm (if None, uses config)
        expires_minutes: Token expiration (if None, uses config)
        config: Optional AuthConfig (if None, uses global .env config)
    """
    cfg = config or get_config()
    secret_key = secret_key or cfg.secret_key
    algorithm = algorithm or cfg.algorithm
    expires_minutes = expires_minutes or cfg.access_token_expire_minutes

    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + timedelta(minutes=expires_minutes)
    if "sub" in to_encode:
        to_encode["sub"] = str(to_encode["sub"])

    to_encode.update({
        "exp": int(expire.timestamp()),
        "iat": int(datetime.now(timezone.utc).timestamp()),
        "type": "access"
    })

    encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=algorithm)
    return encoded_jwt


def create_refresh_token(
    data: Dict[str, Any],
    secret_key: Optional[str] = None,
    algorithm: Optional[str] = None,
    expires_days: Optional[int] = None,
    config: Optional[AuthConfig] = None
) -> str:
    """Create JWT refresh token."""
    cfg = config or get_config()
    secret_key = secret_key or cfg.secret_key
    algorithm = algorithm or cfg.algorithm
    expires_days = expires_days or cfg.refresh_token_expire_days

    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + timedelta(days=expires_days)
    if "sub" in to_encode:
        to_encode["sub"] = str(to_encode["sub"])

    to_encode.update({
        "exp": int(expire.timestamp()),
        "iat": int(datetime.now(timezone.utc).timestamp()),
        "type": "refresh"
    })

    encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=algorithm)
    return encoded_jwt


def decode_token(
    token: str,
    secret_key: Optional[str] = None,
    algorithm: Optional[str] = None,
    config: Optional[AuthConfig] = None
) -> Dict[str, Any]:
    """Decode and verify JWT token."""
    cfg = config or get_config()
    secret_key = secret_key or cfg.secret_key
    algorithm = algorithm or cfg.algorithm

    try:
        payload = jwt.decode(token, secret_key, algorithms=[algorithm])
        return payload
    except JWTError as e:
        raise TokenExpiredError() from e


def verify_token_type(
    token: str,
    expected_type: str,
    secret_key: Optional[str] = None,
    algorithm: Optional[str] = None,
    config: Optional[AuthConfig] = None
) -> Dict[str, Any]:
    """Verify token and check its type."""
    payload = decode_token(token, secret_key, algorithm, config)
    if payload.get("type") != expected_type:
        raise InvalidTokenError()
    return payload


def hash_password(password: str) -> str:
    """Hash password using bcrypt."""
    password_bytes = password.encode('utf-8')
    if len(password_bytes) > 72:
        password = hashlib.sha256(password_bytes).hexdigest()
    return pwd_context.hash(password)


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """Verify password against hash."""
    password_bytes = plain_password.encode('utf-8')
    if len(password_bytes) > 72:
        plain_password = hashlib.sha256(password_bytes).hexdigest()
    return pwd_context.verify(plain_password, hashed_password)


def generate_backup_codes(count: int = 10) -> list[str]:
    """Generate random backup codes."""
    import secrets
    import string
    codes = []
    alphabet = string.ascii_uppercase + string.digits
    for _ in range(count):
        code = ''.join(secrets.choice(alphabet) for _ in range(8))
        codes.append(code)
    return codes


def hash_backup_codes(codes: list[str]) -> list[str]:
    """Hash backup codes for storage."""
    return [hash_password(code) for code in codes]


def verify_backup_code(plain_code: str, hashed_codes: list[str]) -> bool:
    """Verify backup code against hashed codes."""
    for hashed_code in hashed_codes:
        if verify_password(plain_code, hashed_code):
            return True
    return False
