import os
import re
import logging
import secrets
from datetime import datetime, timezone, timedelta
from typing import Optional

from jose import JWTError, ExpiredSignatureError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status, Request
from sqlalchemy.orm import Session

from app.core.database import get_db
from app.models.user import User

logger = logging.getLogger(__name__)

# ── Config ─────────────────────────────────────────────────────────
SECRET_KEY = os.getenv("SECRET_KEY", "")
if not SECRET_KEY:
    raise RuntimeError(
        "[Auth] SECRET_KEY belum diset di environment variable!\n"
        "Tambahkan SECRET_KEY ke file .env atau environment sistem.\n"
        "Contoh: SECRET_KEY=super-secret-random-key-min-32-chars"
    )

ALGORITHM                 = "HS256"
ACCESS_TOKEN_EXPIRE_HOURS = 8

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


# ── Password ────────────────────────────────────────────────────────
def hash_password(password: str) -> str:
    return pwd_context.hash(password)


def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)


# ── JWT Token ───────────────────────────────────────────────────────
def create_access_token(
    data: dict,
    expires_hours: int = ACCESS_TOKEN_EXPIRE_HOURS,
) -> str:
    payload = data.copy()
    payload["exp"] = datetime.now(timezone.utc) + timedelta(hours=expires_hours)
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)


def decode_token(token: str) -> Optional[dict]:
    """
    Decode JWT token.
    Bedakan ExpiredSignatureError dan JWTError lainnya agar log lebih informatif.
    """
    try:
        return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    except ExpiredSignatureError:
        logger.debug("[Auth] Token expired.")
        return None
    except JWTError as exc:
        logger.debug("[Auth] Token tidak valid: %s", exc)
        return None


# ── User lookup ─────────────────────────────────────────────────────
def get_user_by_username(db: Session, username: str) -> Optional[User]:
    return db.query(User).filter(User.username == username).first()


def get_user_by_id(db: Session, user_id: int) -> Optional[User]:
    return db.query(User).filter(User.id == user_id).first()


def get_user_by_email(db: Session, email: str) -> Optional[User]:
    """Cari user berdasarkan email (case-insensitive)."""
    return db.query(User).filter(User.email == email.lower()).first()


def get_user_by_oauth(db: Session, provider: str, oauth_id: str) -> Optional[User]:
    """Cari user berdasarkan kombinasi provider + ID unik dari provider."""
    return db.query(User).filter(
        User.oauth_provider == provider,
        User.oauth_id == str(oauth_id),
    ).first()


def authenticate_user(
    db: Session,
    username: str,
    password: str,
) -> Optional[User]:
    user = get_user_by_username(db, username)
    # Selalu panggil verify_password meski user tidak ada (dummy hash)
    # agar waktu respons konsisten dan tidak bocorkan username terdaftar.
    dummy_hash = "$2b$12$KIXtVDfS0mYFwKWKNbIBEeq6oMsZLY7JpY.m5o5k5k5k5k5k5k5k5"
    hashed     = user.hashed_password if user else dummy_hash

    if not verify_password(password, hashed):
        return None
    if not user or not user.is_active:
        return None
    return user


# ── OAuth: buat atau temukan user ───────────────────────────────────
def get_or_create_oauth_user(
    db:        Session,
    provider:  str,
    oauth_id:  str,
    email:     Optional[str],
    full_name: Optional[str],
) -> User:
    """
    Strategi pencocokan (berurutan):
    1. Cari user dengan oauth_provider + oauth_id yang sama   → login langsung.
    2. Jika ada email, cari user lama yang punya email itu    → hubungkan akun OAuth.
    3. Tidak ada yang cocok                                   → buat akun baru.

    Username dibuat otomatis dari bagian lokal email atau nama,
    dengan angka di belakang jika sudah terpakai.
    """
    # 1. Cari via OAuth ID
    user = get_user_by_oauth(db, provider, oauth_id)
    if user:
        if not user.is_active:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Akun Anda telah dinonaktifkan.",
            )
        return user

    # 2. Cari via email — hubungkan ke akun yang sudah ada
    email_clean = email.lower().strip() if email else None
    if email_clean:
        user = get_user_by_email(db, email_clean)
        if user:
            if not user.is_active:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="Akun Anda telah dinonaktifkan.",
                )
            # Hubungkan akun OAuth ke user yang sudah ada
            user.oauth_provider = provider
            user.oauth_id       = str(oauth_id)
            db.commit()
            db.refresh(user)
            logger.info(
                "[Auth] Akun OAuth %s/%s dihubungkan ke user '%s'.",
                provider, oauth_id, user.username,
            )
            return user

    # 3. Buat akun baru
    # Buat username dasar dari email atau nama
    if email_clean:
        base = email_clean.split("@")[0]
    elif full_name:
        base = full_name.lower().replace(" ", "_")
    else:
        base = provider + "_user"

    # Bersihkan karakter non-alfanumerik
    base = re.sub(r"[^a-z0-9_]", "", base)[:20] or "user"

    username = base
    counter  = 1
    while get_user_by_username(db, username):
        username = f"{base}{counter}"
        counter += 1

    new_user = User(
        username        = username,
        email           = email_clean,
        full_name       = (full_name or "").strip() or None,
        # Password acak — user OAuth tidak punya password lokal
        hashed_password = hash_password(secrets.token_hex(32)),
        role            = "user",
        is_active       = True,
        oauth_provider  = provider,
        oauth_id        = str(oauth_id),
    )
    db.add(new_user)
    db.commit()
    db.refresh(new_user)

    logger.info(
        "[Auth] Akun baru dibuat via OAuth %s: username='%s', email='%s'.",
        provider, username, email_clean,
    )
    return new_user


# ── Dependency: ambil user dari cookie token ────────────────────────
def get_current_user(
    request: Request,
    db: Session = Depends(get_db),
) -> User:
    token = request.cookies.get("access_token")
    if not token:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Belum login.",
            headers={"WWW-Authenticate": "Bearer"},
        )

    payload = decode_token(token)
    if not payload:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token tidak valid atau sudah expired.",
        )

    try:
        user_id = int(payload["sub"])
    except (KeyError, TypeError, ValueError):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token tidak valid: payload rusak.",
        )

    user = get_user_by_id(db, user_id)

    if not user:
        logger.warning("[Auth] Token valid tapi user ID %d tidak ada di DB.", user_id)
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Sesi tidak valid. Silakan login kembali.",
        )
    if not user.is_active:
        logger.warning("[Auth] User ID %d mencoba login tapi akunnya nonaktif.", user_id)
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Akun Anda telah dinonaktifkan.",
        )
    return user


def get_current_admin(
    current_user: User = Depends(get_current_user),
) -> User:
    if current_user.role != "admin":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Akses ditolak. Hanya admin yang diizinkan.",
        )
    return current_user


# ── Seed admin pertama ──────────────────────────────────────────────
def create_default_admin(db: Session) -> None:
    """
    Buat akun admin default jika belum ada user sama sekali.
    Dipanggil saat startup aplikasi (lifespan di main.py).
    Password default dibaca dari env variable ADMIN_DEFAULT_PASSWORD.
    """
    if db.query(User).count() > 0:
        return

    default_password = os.getenv("ADMIN_DEFAULT_PASSWORD", "admin123")

    admin = User(
        username        = "admin",
        email           = "admin@localhost",
        full_name       = "Administrator",
        hashed_password = hash_password(default_password),
        role            = "admin",
        is_active       = True,
    )
    db.add(admin)
    db.commit()

    logger.info("[Auth] Akun admin default dibuat. Username: admin")
    logger.warning(
        "[Auth] ⚠️  Password admin default: '%s'. "
        "Segera ganti melalui panel admin!",
        default_password,
    )
    print("[Auth] Akun admin default dibuat. Username: admin")
    print(f"[Auth] ⚠️  Password default: '{default_password}' — SEGERA GANTI!")