"""Security utilities - JWT and password hashing."""

from datetime import datetime, timedelta, timezone
from typing import Optional
import secrets
import base64

import bcrypt
from jose import JWTError, jwt
from pydantic import EmailStr

from src.core.config import settings


# JWT Configuration
JWT_SECRET_KEY = getattr(
    settings, "jwt_secret_key", "super-secret-change-in-production"
)
JWT_ALGORITHM = getattr(settings, "jwt_algorithm", "HS256")
ACCESS_TOKEN_EXPIRE_MINUTES = getattr(settings, "access_token_expire_minutes", 30)
REFRESH_TOKEN_EXPIRE_DAYS = getattr(settings, "refresh_token_expire_days", 7)


# Password hashing
BCRYPT_ROUNDS = getattr(settings, "bcrypt_rounds", 12)


def hash_password(password: str) -> str:
    """Hash a password using bcrypt.

    Args:
        password: Plain text password

    Returns:
        Hashed password string
    """
    password_bytes = password.encode("utf-8")
    salt = bcrypt.gensalt(rounds=BCRYPT_ROUNDS)
    hashed = bcrypt.hashpw(password_bytes, salt)
    return hashed.decode("utf-8")


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """Verify a password against a hash.

    Args:
        plain_password: Plain text password
        hashed_password: Hashed password string

    Returns:
        True if password matches, False otherwise
    """
    password_bytes = plain_password.encode("utf-8")
    hashed_bytes = hashed_password.encode("utf-8")
    return bcrypt.checkpw(password_bytes, hashed_bytes)


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
    """Create a JWT access token.

    Args:
        data: Data to encode in the token
        expires_delta: Optional custom expiration time

    Returns:
        JWT token string
    """
    to_encode = data.copy()

    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(
            minutes=ACCESS_TOKEN_EXPIRE_MINUTES
        )

    to_encode.update({"exp": expire, "type": "access"})
    encoded_jwt = jwt.encode(to_encode, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM)
    return encoded_jwt


def create_refresh_token(data: dict) -> str:
    """Create a JWT refresh token.

    Args:
        data: Data to encode in the token

    Returns:
        JWT refresh token string
    """
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
    to_encode.update({"exp": expire, "type": "refresh"})
    encoded_jwt = jwt.encode(to_encode, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM)
    return encoded_jwt


def verify_token(token: str) -> Optional[dict]:
    """Verify and decode a JWT token.

    Args:
        token: JWT token string

    Returns:
        Decoded payload dict or None if invalid
    """
    try:
        payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM])
        return payload
    except JWTError:
        return None


def verify_access_token(token: str) -> Optional[dict]:
    """Verify an access token specifically.

    Args:
        token: JWT access token string

    Returns:
        Decoded payload dict or None if invalid
    """
    payload = verify_token(token)
    if payload and payload.get("type") == "access":
        return payload
    return None


def verify_refresh_token(token: str) -> Optional[dict]:
    """Verify a refresh token specifically.

    Args:
        token: JWT refresh token string

    Returns:
        Decoded payload dict or None if invalid
    """
    payload = verify_token(token)
    if payload and payload.get("type") == "refresh":
        return payload
    return None


def generate_api_key() -> tuple[str, str]:
    """Generate a new API key and its hash.

    Returns:
        Tuple of (full_key, key_hash)
        - full_key: The complete API key to show once (mk_ + base64)
        - key_hash: SHA-256 hash to store in database
    """
    # Generate 32 random bytes
    random_bytes = secrets.token_bytes(32)
    # Encode to base64 (URL-safe)
    key_part = base64.urlsafe_b64encode(random_bytes).decode("utf-8").rstrip("=")
    # Full key with prefix
    full_key = f"mk_{key_part}"
    # Create hash for storage (using bcrypt for security)
    key_hash = bcrypt.hashpw(
        full_key.encode("utf-8"), bcrypt.gensalt(rounds=12)
    ).decode("utf-8")
    # Prefix for identification (first 8 chars after mk_)
    return full_key, key_hash


def get_key_prefix(key: str) -> str:
    """Extract prefix from API key for identification.

    Args:
        key: Full API key

    Returns:
        First 8 characters of the key part (after mk_)
    """
    if key.startswith("mk_"):
        key_part = key[3:]  # Remove "mk_" prefix
        return key_part[:8]
    return key[:8]


def verify_api_key(key: str, key_hash: str) -> bool:
    """Verify an API key against its stored hash.

    Args:
        key: Full API key
        key_hash: Stored bcrypt hash

    Returns:
        True if key matches, False otherwise
    """
    return bcrypt.checkpw(key.encode("utf-8"), key_hash.encode("utf-8"))


def validate_email_format(email: str) -> bool:
    """Validate email format.

    Args:
        email: Email string to validate

    Returns:
        True if valid email format, False otherwise
    """
    try:
        EmailStr._validate(email)
        return True
    except Exception:
        return False
