import logging

from fastapi import APIRouter, Depends, HTTPException, Request, Form, Query, status
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session

from app.core.database import get_db
from app.models.user import User
from app.models.transaksi import Transaction  # Import model transaksi
from app.services.auth_service import (
    authenticate_user,
    create_access_token,
    get_current_user,
    get_current_admin,
    get_user_by_username,
    get_user_by_email,
    get_or_create_oauth_user,
    hash_password,
    verify_password,
)
from app.services.oauth_service import (
    generate_state,
    is_google_enabled,
    is_github_enabled,
    build_google_auth_url,
    build_github_auth_url,
    exchange_google_code,
    exchange_github_code,
)
from app.core.config import ALLOW_PUBLIC_SIGNUP

router    = APIRouter()
templates = Jinja2Templates(directory="app/templates")
logger    = logging.getLogger(__name__)

# Cookie name untuk menyimpan OAuth state (CSRF protection)
_OAUTH_STATE_COOKIE = "oauth_state"
# Durasi cookie state dalam detik (5 menit)
_OAUTH_STATE_TTL    = 300


# ── Helper internal ─────────────────────────────────────────────────

def _set_auth_cookie(response: RedirectResponse, user: User) -> None:
    """Tambahkan cookie JWT ke response."""
    token = create_access_token({"sub": str(user.id), "role": user.role})
    response.set_cookie(
        key      = "access_token",
        value    = token,
        httponly = True,
        max_age  = 8 * 3600,
        samesite = "lax",
    )


def _redirect_after_login(user: User) -> str:
    """Tentukan URL tujuan setelah login berhasil."""
    return "/admin" if user.role == "admin" else "/home"


def _oauth_error_redirect(message: str) -> RedirectResponse:
    """Redirect ke halaman login dengan pesan error OAuth."""
    from urllib.parse import quote
    return RedirectResponse(
        url        = f"/login?oauth_error={quote(message)}",
        status_code = 302,
    )


# ================================================================
# GET /login — Halaman login
# ================================================================
@router.get("/login", response_class=HTMLResponse)
def login_page(request: Request):
    token = request.cookies.get("access_token")
    if token:
        return RedirectResponse(url="/home", status_code=302)

    return templates.TemplateResponse("login.html", {
        "request":       request,
        "error":         request.query_params.get("error"),
        "oauth_error":   request.query_params.get("oauth_error"),
        "google_enabled": is_google_enabled(),
        "github_enabled": is_github_enabled(),
        "signup_enabled": ALLOW_PUBLIC_SIGNUP,
    })


# ================================================================
# POST /login — Proses login
# ================================================================
@router.post("/login")
def do_login(
    request:  Request,
    username: str     = Form(...),
    password: str     = Form(...),
    db:       Session = Depends(get_db),
):
    user = authenticate_user(db, username, password)
    if not user:
        return templates.TemplateResponse(
            "login.html",
            {
                "request":        request,
                "error":          "Username atau password salah.",
                "google_enabled": is_google_enabled(),
                "github_enabled": is_github_enabled(),
                "signup_enabled": ALLOW_PUBLIC_SIGNUP,
            },
            status_code=400,
        )

    response = RedirectResponse(url=_redirect_after_login(user), status_code=302)
    _set_auth_cookie(response, user)
    return response


# ================================================================
# GET /logout
# ================================================================
@router.get("/logout")
def logout():
    response = RedirectResponse(url="/login", status_code=302)
    response.delete_cookie("access_token")
    return response


# ================================================================
# GET /signup — Halaman daftar akun (publik)
# ================================================================
@router.get("/signup", response_class=HTMLResponse)
def signup_page(request: Request):
    if not ALLOW_PUBLIC_SIGNUP:
        return RedirectResponse(url="/login", status_code=302)

    token = request.cookies.get("access_token")
    if token:
        return RedirectResponse(url="/home", status_code=302)

    return templates.TemplateResponse("signup.html", {
        "request":        request,
        "error":          None,
        "google_enabled": is_google_enabled(),
        "github_enabled": is_github_enabled(),
    })


# ================================================================
# POST /signup — Proses pendaftaran akun baru
# ================================================================
@router.post("/signup")
def do_signup(
    request:   Request,
    username:  str = Form(...),
    password:  str = Form(...),
    password2: str = Form(...),
    full_name: str = Form(default=""),
    email:     str = Form(default=""),
    db:        Session = Depends(get_db),
):
    if not ALLOW_PUBLIC_SIGNUP:
        raise HTTPException(status_code=403, detail="Pendaftaran publik tidak diaktifkan.")

    def render_error(msg: str):
        return templates.TemplateResponse(
            "signup.html",
            {
                "request":        request,
                "error":          msg,
                "google_enabled": is_google_enabled(),
                "github_enabled": is_github_enabled(),
                # Pertahankan input agar user tidak perlu mengisi ulang
                "form": {
                    "username":  username,
                    "full_name": full_name,
                    "email":     email,
                },
            },
            status_code=400,
        )

    # ── Validasi input ─────────────────────────────────────────────
    username = username.strip()
    if not username:
        return render_error("Username tidak boleh kosong.")
    if len(username) < 3:
        return render_error("Username minimal 3 karakter.")
    if len(username) > 30:
        return render_error("Username maksimal 30 karakter.")

    import re
    if not re.match(r"^[a-zA-Z0-9_]+$", username):
        return render_error("Username hanya boleh mengandung huruf, angka, dan underscore.")

    if len(password) < 6:
        return render_error("Password minimal 6 karakter.")
    if password != password2:
        return render_error("Konfirmasi password tidak cocok.")

    email_clean = email.strip().lower() or None
    if email_clean and ("@" not in email_clean or "." not in email_clean):
        return render_error("Format email tidak valid.")

    # ── Cek duplikat ───────────────────────────────────────────────
    if get_user_by_username(db, username):
        return render_error("Username sudah digunakan. Pilih username lain.")
    if email_clean and get_user_by_email(db, email_clean):
        return render_error("Email sudah terdaftar. Silakan login atau gunakan email lain.")

    # ── Buat akun ─────────────────────────────────────────────────
    new_user = User(
        username        = username,
        email           = email_clean,
        full_name       = full_name.strip() or None,
        hashed_password = hash_password(password),
        role            = "user",
        is_active       = True,
    )
    db.add(new_user)
    db.commit()
    db.refresh(new_user)

    logger.info("[Signup] Akun baru dibuat: username='%s'.", username)

    # Login otomatis setelah daftar
    response = RedirectResponse(url="/home", status_code=302)
    _set_auth_cookie(response, new_user)
    return response


# ================================================================
# GET /auth/google — Mulai OAuth Google
# ================================================================
@router.get("/auth/google")
def google_login():
    if not is_google_enabled():
        return RedirectResponse(url="/login?oauth_error=Google+OAuth+belum+dikonfigurasi.", status_code=302)

    state    = generate_state()
    auth_url = build_google_auth_url(state)

    response = RedirectResponse(url=auth_url, status_code=302)
    response.set_cookie(
        key      = _OAUTH_STATE_COOKIE,
        value    = state,
        httponly = True,
        max_age  = _OAUTH_STATE_TTL,
        samesite = "lax",
    )
    return response


# ================================================================
# GET /auth/google/callback — Callback Google
# ================================================================
@router.get("/auth/google/callback")
async def google_callback(
    request: Request,
    db:      Session = Depends(get_db),
    code:    str     = Query(default=""),
    state:   str     = Query(default=""),
    error:   str     = Query(default=""),
):
    # ── Tangani penolakan user ────────────────────────────────────
    if error:
        return _oauth_error_redirect("Login Google dibatalkan.")

    # ── Verifikasi state (CSRF) ───────────────────────────────────
    stored_state = request.cookies.get(_OAUTH_STATE_COOKIE)
    if not stored_state or stored_state != state:
        logger.warning("[OAuth] Google: state tidak cocok — kemungkinan CSRF.")
        return _oauth_error_redirect("Verifikasi keamanan gagal. Silakan coba lagi.")

    if not code:
        return _oauth_error_redirect("Tidak menerima kode otorisasi dari Google.")

    # ── Tukar code dengan user info ───────────────────────────────
    user_info = await exchange_google_code(code)
    if not user_info:
        logger.error("[OAuth] Google: gagal menukar code dengan user info.")
        return _oauth_error_redirect("Gagal mengambil informasi akun dari Google.")

    oauth_id  = user_info.get("sub")
    email     = user_info.get("email")
    full_name = user_info.get("name", "")

    if not oauth_id:
        return _oauth_error_redirect("Respons Google tidak lengkap.")

    # ── Buat atau temukan user ────────────────────────────────────
    try:
        user = get_or_create_oauth_user(db, "google", oauth_id, email, full_name)
    except HTTPException as exc:
        return _oauth_error_redirect(exc.detail)

    # ── Login ─────────────────────────────────────────────────────
    response = RedirectResponse(url=_redirect_after_login(user), status_code=302)
    _set_auth_cookie(response, user)
    response.delete_cookie(_OAUTH_STATE_COOKIE)

    logger.info("[OAuth] Google login berhasil: user='%s'.", user.username)
    return response


# ================================================================
# GET /auth/github — Mulai OAuth GitHub
# ================================================================
@router.get("/auth/github")
def github_login():
    if not is_github_enabled():
        return RedirectResponse(url="/login?oauth_error=GitHub+OAuth+belum+dikonfigurasi.", status_code=302)

    state    = generate_state()
    auth_url = build_github_auth_url(state)

    response = RedirectResponse(url=auth_url, status_code=302)
    response.set_cookie(
        key      = _OAUTH_STATE_COOKIE,
        value    = state,
        httponly = True,
        max_age  = _OAUTH_STATE_TTL,
        samesite = "lax",
    )
    return response


# ================================================================
# GET /auth/github/callback — Callback GitHub
# ================================================================
@router.get("/auth/github/callback")
async def github_callback(
    request: Request,
    db:      Session = Depends(get_db),
    code:    str     = Query(default=""),
    state:   str     = Query(default=""),
    error:   str     = Query(default=""),
):
    # ── Tangani penolakan user ────────────────────────────────────
    if error:
        return _oauth_error_redirect("Login GitHub dibatalkan.")

    # ── Verifikasi state (CSRF) ───────────────────────────────────
    stored_state = request.cookies.get(_OAUTH_STATE_COOKIE)
    if not stored_state or stored_state != state:
        logger.warning("[OAuth] GitHub: state tidak cocok — kemungkinan CSRF.")
        return _oauth_error_redirect("Verifikasi keamanan gagal. Silakan coba lagi.")

    if not code:
        return _oauth_error_redirect("Tidak menerima kode otorisasi dari GitHub.")

    # ── Tukar code dengan user info ───────────────────────────────
    user_info = await exchange_github_code(code)
    if not user_info:
        logger.error("[OAuth] GitHub: gagal menukar code dengan user info.")
        return _oauth_error_redirect("Gagal mengambil informasi akun dari GitHub.")

    oauth_id  = str(user_info.get("id", ""))
    email     = user_info.get("email")
    full_name = user_info.get("name") or user_info.get("login", "")

    if not oauth_id:
        return _oauth_error_redirect("Respons GitHub tidak lengkap.")

    # ── Buat atau temukan user ────────────────────────────────────
    try:
        user = get_or_create_oauth_user(db, "github", oauth_id, email, full_name)
    except HTTPException as exc:
        return _oauth_error_redirect(exc.detail)

    # ── Login ─────────────────────────────────────────────────────
    response = RedirectResponse(url=_redirect_after_login(user), status_code=302)
    _set_auth_cookie(response, user)
    response.delete_cookie(_OAUTH_STATE_COOKIE)

    logger.info("[OAuth] GitHub login berhasil: user='%s'.", user.username)
    return response


# ================================================================
# POST /api/auth/register — Buat user baru (hanya admin)
# ================================================================
@router.post("/api/auth/register")
def register_user(
    username:  str     = Form(...),
    password:  str     = Form(...),
    full_name: str     = Form(default=""),
    email:     str     = Form(default=""),
    role:      str     = Form(default="user"),
    db:        Session = Depends(get_db),
    current_admin: User = Depends(get_current_admin),
):
    if get_user_by_username(db, username):
        raise HTTPException(status_code=400, detail="Username sudah digunakan.")

    if role not in ("user", "admin"):
        raise HTTPException(status_code=400, detail="Role tidak valid.")

    new_user = User(
        username        = username,
        email           = email or None,
        full_name       = full_name or None,
        hashed_password = hash_password(password),
        role            = role,
        is_active       = True,
    )
    db.add(new_user)
    db.commit()
    db.refresh(new_user)

    return {"success": True, "user": new_user.to_dict()}


# ================================================================
# POST /api/auth/change-password — Ganti password sendiri
# ================================================================
@router.post("/api/auth/change-password")
def change_password(
    old_password: str     = Form(...),
    new_password: str     = Form(...),
    db:           Session = Depends(get_db),
    current_user: User    = Depends(get_current_user),
):
    # User OAuth tidak punya password lokal; tolak dengan pesan jelas.
    if current_user.oauth_provider:
        raise HTTPException(
            status_code=400,
            detail=(
                f"Akun Anda terhubung via {current_user.oauth_provider.capitalize()}. "
                "Ubah password melalui provider tersebut."
            ),
        )

    if not verify_password(old_password, current_user.hashed_password):
        raise HTTPException(status_code=400, detail="Password lama salah.")

    if len(new_password) < 6:
        raise HTTPException(status_code=400, detail="Password baru minimal 6 karakter.")

    current_user.hashed_password = hash_password(new_password)
    db.commit()
    return {"success": True, "message": "Password berhasil diubah."}


# ================================================================
# GET /api/auth/me — Info user yang sedang login + saldo
# ================================================================
@router.get("/api/auth/me")
def me(current_user: User = Depends(get_current_user)):
    """Mengembalikan data user termasuk saldo."""
    data = current_user.to_dict()
    data["balance"] = current_user.balance   # asumsikan model User memiliki kolom balance
    return data


# ================================================================
# GET /api/auth/transactions — Riwayat transaksi user
# ================================================================
@router.get("/api/auth/transactions")
def get_transactions(
    limit:        int = Query(default=50, ge=1, le=200),
    offset:       int = Query(default=0, ge=0),
    db:           Session = Depends(get_db),
    current_user: User    = Depends(get_current_user),
):
    """Mengembalikan daftar transaksi (top-up, pemakaian) untuk user yang sedang login."""
    query = db.query(Transaction).filter(Transaction.user_id == current_user.id).order_by(Transaction.created_at.desc())
    total = query.count()
    items = query.offset(offset).limit(limit).all()

    return {
        "total": total,
        "limit": limit,
        "offset": offset,
        "transactions": [
            {
                "id": t.id,
                "amount": t.amount,              # positif = tambah saldo, negatif = pemakaian
                "service_type": t.service_type,  # topup, transcription, ba
                "description": t.description,
                "created_at": t.created_at.isoformat() if t.created_at else None
            } for t in items
        ]
    }