"""Public routes: apply, status check, validate coupon, public settings"""
import os
import logging
from pathlib import Path
from typing import Optional
from datetime import datetime, timezone
from fastapi import APIRouter, HTTPException, UploadFile, File, Form, Depends
from pydantic import EmailStr, ValidationError

from models import (
    ApplicationCreate,
    Application,
    ApplicationPublicView,
    CouponValidateRequest,
    Settings,
)
from db import get_db

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api", tags=["public"])

UPLOAD_DIR = Path(os.environ.get("UPLOAD_DIR", "/app/backend/uploads"))
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/jpg", "image/png", "image/webp"}
MAX_IMAGE_BYTES = 10 * 1024 * 1024  # 10 MB


def _ext_for(content_type: str) -> str:
    return {
        "image/jpeg": ".jpg",
        "image/jpg": ".jpg",
        "image/png": ".png",
        "image/webp": ".webp",
    }.get(content_type, ".jpg")


async def _save_upload(file: UploadFile, app_id: str, kind: str) -> str:
    if file.content_type not in ALLOWED_IMAGE_TYPES:
        raise HTTPException(status_code=400, detail=f"Unsupported {kind} image type: {file.content_type}")
    contents = await file.read()
    if len(contents) > MAX_IMAGE_BYTES:
        raise HTTPException(status_code=400, detail=f"{kind} image too large (max 10MB)")
    if len(contents) < 1024:
        raise HTTPException(status_code=400, detail=f"{kind} image too small / corrupt")
    folder = UPLOAD_DIR / app_id
    folder.mkdir(parents=True, exist_ok=True)
    ext = _ext_for(file.content_type)
    path = folder / f"{kind}{ext}"
    with open(path, "wb") as f:
        f.write(contents)
    return str(path.relative_to(UPLOAD_DIR))


async def _get_settings(db) -> Settings:
    doc = await db.settings.find_one({"id": "global"}, {"_id": 0})
    if not doc:
        s = Settings()
        await db.settings.insert_one(s.model_dump())
        return s
    return Settings(**doc)


@router.get("/settings/public")
async def public_settings(db=Depends(get_db)):
    s = await _get_settings(db)
    return {
        "site_name": s.site_name,
        "agent_fee_usd": s.agent_fee_usd,
        "telegram_manager_username": s.telegram_manager_username,
        "telegram_manager_url": s.telegram_manager_url,
    }


@router.get("/app/config")
async def app_page_config(db=Depends(get_db)):
    """Public — returns the App Download page configuration (links, branding, bonus)."""
    s = await _get_settings(db)
    return {
        "active": s.app_page_active,
        "brand_name": s.app_brand_name,
        "tagline": s.app_tagline,
        "android_url": s.app_android_url,
        "ios_url": s.app_ios_url,
        "register_url": s.app_register_url,
        "welcome_bonus": s.app_welcome_bonus,
        "min_deposit": s.app_min_deposit,
        "user_count": s.app_user_count,
        "rating": s.app_rating,
        "apk_version": s.app_apk_version,
        "apk_size": s.app_apk_size,
        "register_cta_label": s.app_register_cta_label,
        "promo_label": s.app_promo_label,
        "disclaimer": s.app_disclaimer,
        "jackpot_amount": s.app_jackpot_amount,
        "telegram_manager_username": s.telegram_manager_username,
        "telegram_manager_url": s.telegram_manager_url,
    }



@router.get("/public/stats")
async def public_stats(db=Depends(get_db)):
    """Landing-page counters. Admin-overridable via settings; falls back to real DB values."""
    s = await _get_settings(db)
    approved = await db.applications.count_documents({"status": "approved"})
    paid = await db.applications.count_documents({"status": "paid"})
    return {
        "active_agents_label": (s.landing_active_agents or "").strip() or str(approved),
        "active_agents_real": approved,
        "applications_in_review": paid,
        "total_processed": approved + paid,
        "uptime_pct_label": s.landing_uptime_pct or "99.9%",
        "avg_approval_label": s.landing_avg_approval or "24h",
        "cities_covered_label": s.landing_cities_covered or "30+",
    }


@router.get("/public/landing")
async def public_landing(db=Depends(get_db)):
    """Editable landing-page content (hero, benefits, FAQs, sections, footer)."""
    s = await _get_settings(db)
    return s.model_dump()



@router.post("/applications", response_model=ApplicationPublicView)
async def submit_application(
    full_name: str = Form(...),
    email: EmailStr = Form(...),
    phone: str = Form(...),
    username: str = Form(...),
    city: str = Form(...),
    state: str = Form(...),
    telegram_username: str = Form(...),
    nid_front: UploadFile = File(...),
    nid_back: UploadFile = File(...),
    nid_selfie: UploadFile = File(...),
    db=Depends(get_db),
):
    """Submit an agent application. Returns the new application with id.

    Files are saved under /uploads/{application_id}/.
    """
    try:
        payload = ApplicationCreate(
            full_name=full_name.strip(),
            email=email,
            phone=phone.strip(),
            username=username.strip(),
            city=city.strip(),
            state=state.strip(),
            telegram_username=telegram_username.strip().lstrip("@"),
        )
    except ValidationError as ve:
        first = ve.errors()[0]
        field = first.get("loc", ["field"])[-1]
        msg = first.get("msg", "Invalid value")
        raise HTTPException(
            status_code=400,
            detail=(
                f"{field}: {msg} / "
                f"'{field}' ফিল্ডটি সঠিকভাবে পূরণ করুন।"
            ),
        )
    existing = await db.applications.find_one(
        {"$or": [{"username": payload.username}, {"email": payload.email}], "status": {"$in": ["paid", "approved"]}},
        {"_id": 0, "id": 1, "status": 1},
    )
    if existing:
        raise HTTPException(
            status_code=409,
            detail="An application with this email/username is already submitted or active.",
        )

    # ── Detect previously-paid (now rejected) application — if so the new
    # submission inherits the paid status (no re-payment needed). The user
    # already paid once; rejection was due to NID issues which are now fixed
    # via this new submission.
    prior_paid = await db.applications.find_one(
        {
            "$or": [{"username": payload.username}, {"email": payload.email.lower()}, {"email": payload.email}],
            "status": "rejected",
            "paid_at": {"$ne": None},
        },
        {"_id": 0},
        sort=[("paid_at", -1)],
    )

    settings = await _get_settings(db)
    app = Application(
        full_name=payload.full_name,
        email=payload.email,
        phone=payload.phone,
        username=payload.username,
        city=payload.city,
        state=payload.state,
        telegram_username=payload.telegram_username,
        base_amount=settings.agent_fee_usd,
        price_amount=settings.agent_fee_usd,
    )

    # If prior payment exists → carry it forward, skip payment screen
    if prior_paid:
        now_iso = datetime.now(timezone.utc).isoformat()
        app.status = "paid"
        app.payment_id = prior_paid.get("payment_id")
        app.payment_status = prior_paid.get("payment_status") or "finished"
        app.pay_address = prior_paid.get("pay_address")
        app.pay_amount = prior_paid.get("pay_amount")
        app.pay_currency = prior_paid.get("pay_currency")
        app.price_amount = prior_paid.get("price_amount", settings.agent_fee_usd)
        app.base_amount = prior_paid.get("base_amount", settings.agent_fee_usd)
        app.discount_amount = prior_paid.get("discount_amount", 0.0)
        app.coupon_code = prior_paid.get("coupon_code")
        app.paid_at = prior_paid.get("paid_at")
        app.updated_at = now_iso

    front_path = await _save_upload(nid_front, app.id, "nid_front")
    back_path = await _save_upload(nid_back, app.id, "nid_back")
    selfie_path = await _save_upload(nid_selfie, app.id, "nid_selfie")
    app.nid_front_path = front_path
    app.nid_back_path = back_path
    app.nid_selfie_path = selfie_path

    await db.applications.insert_one(app.model_dump())
    return ApplicationPublicView(
        id=app.id,
        full_name=app.full_name,
        email=app.email,
        status=app.status,
        payment_status=app.payment_status,
        price_amount=app.price_amount,
        discount_amount=app.discount_amount,
        created_at=app.created_at,
    )


@router.post("/applications/status")
async def check_status(req: dict, db=Depends(get_db)):
    """Lookup status by application_id + email."""
    app_id = (req.get("application_id") or "").strip()
    email = (req.get("email") or "").strip().lower()
    if not app_id or not email:
        raise HTTPException(status_code=400, detail="application_id and email required")
    doc = await db.applications.find_one({"id": app_id}, {"_id": 0})
    if not doc or doc.get("email", "").lower() != email:
        raise HTTPException(status_code=404, detail="No matching application found")
    return {
        "id": doc["id"],
        "full_name": doc["full_name"],
        "email": doc["email"],
        "status": doc["status"],
        "payment_status": doc.get("payment_status"),
        "price_amount": doc.get("price_amount", 0),
        "discount_amount": doc.get("discount_amount", 0),
        "rejection_reason": doc.get("rejection_reason"),
        "created_at": doc.get("created_at"),
        "decided_at": doc.get("decided_at"),
        "paid_at": doc.get("paid_at"),
    }


@router.post("/coupons/validate")
async def validate_coupon(req: CouponValidateRequest, db=Depends(get_db)):
    code = req.code.strip().upper()
    if not code:
        raise HTTPException(status_code=400, detail="Coupon code required")
    doc = await db.coupons.find_one({"code": code}, {"_id": 0})
    if not doc:
        raise HTTPException(status_code=404, detail="Coupon not found")
    if not doc.get("active", True):
        raise HTTPException(status_code=400, detail="Coupon is inactive")
    if doc.get("max_uses", 0) > 0 and doc.get("used_count", 0) >= doc["max_uses"]:
        raise HTTPException(status_code=400, detail="Coupon usage limit reached")
    expires_at = doc.get("expires_at")
    if expires_at:
        try:
            exp = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
            if exp < datetime.now(timezone.utc):
                raise HTTPException(status_code=400, detail="Coupon expired")
        except ValueError:
            pass
    return {
        "code": doc["code"],
        "discount_percent": doc["discount_percent"],
        "description": doc.get("description"),
        "valid": True,
    }



@router.post("/applications/{app_id}/reupload-nid")
async def reupload_nid(
    app_id: str,
    email: EmailStr = Form(...),
    nid_front: UploadFile = File(...),
    nid_back: UploadFile = File(...),
    nid_selfie: UploadFile = File(...),
    db=Depends(get_db),
):
    """Re-upload NID images for a rejected application without re-paying.

    Verifies the email matches, requires status='rejected'. After successful upload
    images are replaced and status is reset to 'paid' so admin can re-review.
    """
    doc = await db.applications.find_one({"id": app_id}, {"_id": 0})
    if not doc:
        raise HTTPException(status_code=404, detail="Application not found")
    if doc.get("email", "").lower() != str(email).lower():
        raise HTTPException(status_code=403, detail="Email does not match application")
    if doc.get("status") != "rejected":
        raise HTTPException(
            status_code=400,
            detail=f"Re-upload only allowed for rejected applications (current: {doc.get('status')})",
        )

    front_path = await _save_upload(nid_front, app_id, "nid_front")
    back_path = await _save_upload(nid_back, app_id, "nid_back")
    selfie_path = await _save_upload(nid_selfie, app_id, "nid_selfie")

    now_iso = datetime.now(timezone.utc).isoformat()
    await db.applications.update_one(
        {"id": app_id},
        {"$set": {
            "nid_front_path": front_path,
            "nid_back_path": back_path,
            "nid_selfie_path": selfie_path,
            "status": "paid",  # back to under-review (payment is preserved)
            "rejection_reason": None,
            "decided_at": None,
            "reuploaded_at": now_iso,
            "updated_at": now_iso,
        }},
    )

    # Notify admin about re-upload (best-effort)
    try:
        from email_service import send_admin_new_application_email
        settings = await db.settings.find_one({"id": "global"}, {"_id": 0}) or {}
        notify_to = settings.get("notification_email") or os.environ.get("ADMIN_EMAIL")
        if notify_to:
            updated = await db.applications.find_one({"id": app_id}, {"_id": 0})
            await send_admin_new_application_email(notify_to, updated)
    except Exception as e:
        logger.warning("re-upload admin notify failed: %s", e)

    return {"ok": True, "status": "paid", "message": "NID re-uploaded · application back in review"}
