"""Admin routes: user CRUD + settings CRUD. Admin-only."""
import uuid
from datetime import datetime, timedelta, timezone
from typing import List, Optional

from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel, Field

from core.auth import hash_password, require_admin
from routes.auth import _shape_user, get_db

router = APIRouter(prefix="/api/admin", tags=["admin"], dependencies=[Depends(require_admin)])


# ---- User models ----
class UserCreate(BaseModel):
    username: str = Field(min_length=2, max_length=40)
    password: str = Field(min_length=3, max_length=200)
    days: int = Field(default=30, ge=1, le=3650)
    xtream_mode: str = Field(default="shared", pattern="^(shared|own)$")
    xtream_server: Optional[str] = None
    xtream_username: Optional[str] = None
    xtream_password: Optional[str] = None
    note: Optional[str] = None
    # ── Hybrid Dual-Source fields ──
    live_source_type: Optional[str] = Field(default=None, pattern="^(xtream|m3u|stalker)$")
    live_url: Optional[str] = None
    live_user: Optional[str] = None
    live_pass: Optional[str] = None
    live_mac: Optional[str] = None
    vod_source_type: Optional[str] = Field(default=None, pattern="^(xtream|m3u|stalker)$")
    vod_url: Optional[str] = None
    vod_user: Optional[str] = None
    vod_pass: Optional[str] = None
    vod_mac: Optional[str] = None


class UserUpdate(BaseModel):
    password: Optional[str] = Field(default=None, min_length=3, max_length=200)
    extend_days: Optional[int] = Field(default=None, ge=-3650, le=3650)
    set_expiry_at: Optional[str] = None  # ISO datetime
    xtream_mode: Optional[str] = Field(default=None, pattern="^(shared|own)$")
    xtream_server: Optional[str] = None
    xtream_username: Optional[str] = None
    xtream_password: Optional[str] = None
    note: Optional[str] = None
    ads_enabled: Optional[bool] = None
    # ── Hybrid Dual-Source fields ──
    live_source_type: Optional[str] = Field(default=None, pattern="^(xtream|m3u|stalker)$")
    live_url: Optional[str] = None
    live_user: Optional[str] = None
    live_pass: Optional[str] = None
    live_mac: Optional[str] = None
    vod_source_type: Optional[str] = Field(default=None, pattern="^(xtream|m3u|stalker)$")
    vod_url: Optional[str] = None
    vod_user: Optional[str] = None
    vod_pass: Optional[str] = None
    vod_mac: Optional[str] = None


# ---- Settings models ----
class SharedProfileIn(BaseModel):
    id: Optional[str] = None
    name: str
    kind: str = Field(pattern="^(xtream|m3u|stalker)$")
    server: Optional[str] = ""
    username: Optional[str] = ""
    password: Optional[str] = ""
    m3u_url: Optional[str] = ""
    # Raw text content of an uploaded .m3u/.m3u8 file. Optional. When set,
    # apps will use this directly instead of fetching m3u_url.
    m3u_content: Optional[str] = ""
    # Stalker / MAC portal credentials
    portal_url: Optional[str] = ""
    mac_address: Optional[str] = ""


class SettingsUpdate(BaseModel):
    whatsapp_number: Optional[str] = None
    shared_xtream_server: Optional[str] = None
    shared_xtream_username: Optional[str] = None
    shared_xtream_password: Optional[str] = None
    shared_profiles: Optional[List[SharedProfileIn]] = None
    # Admin-controlled toggle. When True, videos launch in an external app
    # (VLC / MX Player / Brouken). When False (default), the in-app ExoPlayer
    # activity is used. Exposed read-only to users via /api/public/playback-mode.
    external_player_enabled: Optional[bool] = None
    # Admin-controlled toggle for the "Install a video player" popup shown on
    # first launch. Default False (opt-in). When False, the popup never appears.
    player_notice_enabled: Optional[bool] = None
    # Admin-controlled toggle for the inline preview player on Live TV.
    # When True, selecting a channel shows a small preview player with
    # gold-icon controls (prev / play-pause / next / fullscreen) instead
    # of launching fullscreen immediately. Default True.
    inline_preview_enabled: Optional[bool] = None
    # ── In-app update / about / support fields (publicly readable via /api/public/app-info) ──
    update_enabled: Optional[bool] = None
    app_version: Optional[str] = None              # latest published version e.g. "1.2.2"
    min_supported_version: Optional[str] = None    # blocks app below this version e.g. "1.0.0"
    update_apk_url: Optional[str] = None           # direct .apk download URL
    update_play_url: Optional[str] = None          # Google Play Store URL
    update_changelog: Optional[str] = None         # multi-line "what's new"
    popup_delay_seconds: Optional[int] = Field(default=None, ge=0, le=600)  # delay before showing update popup
    whatsapp_message: Optional[str] = None         # prefilled WhatsApp text
    privacy_policy_text: Optional[str] = None      # editable privacy policy (markdown/plain)
    support_email: Optional[str] = None
    creator_name: Optional[str] = None
    creator_role: Optional[str] = None
    creator_avatar_url: Optional[str] = None
    creator_bio: Optional[str] = None
    # ── Ads / AdMob (live-editable from the admin dashboard) ──
    # When Pydantic v2 receives an unknown field, it silently drops it by
    # default. Listing these explicitly ensures the admin PUT /settings
    # request actually persists ad-related edits (including the new Native
    # Advanced Ad Unit ID for the "Sponsored" tiles interleaved in the
    # movie/series/live grids on native Android builds).
    ads_enabled: Optional[bool] = None
    # ── BUG-2 FIX (2026-05): Users-vs-Guests ads toggles ──
    # The legacy `ads_enabled` flag is a master kill-switch (ads off
    # entirely when False). These two sub-flags split it further so the
    # admin can decide independently whether ads are shown to logged-in
    # registered users vs guest / not-logged-in visitors. Defaults are
    # chosen to preserve the pre-fix behaviour (guests=ads on, users=
    # ads off — premium accounts stay clean unless admin opts in).
    ads_enabled_for_users: Optional[bool] = None
    ads_enabled_for_guests: Optional[bool] = None
    ads_testing_mode: Optional[bool] = None
    ads_app_id: Optional[str] = None
    ads_banner_id: Optional[str] = None
    ads_interstitial_id: Optional[str] = None
    ads_native_id: Optional[str] = None
    # Rewarded ad unit — used either as a dedicated rewarded flow (e.g.
    # "watch ad to unlock premium for 1 hour") OR as an interstitial
    # fallback when the regular interstitial slot is empty. Live-editable
    # from the admin panel — no APK rebuild required.
    ads_rewarded_id: Optional[str] = None
    ads_interstitial_every: Optional[int] = Field(default=None, ge=0, le=100)
    # ── Terms & Conditions (admin-editable, surfaced by About menu modal) ──
    terms_policy_text: Optional[str] = None
    # ── Smart-TV "House Ad" banner (FEATURE 2026-01) ──
    # AdMob is mobile-only, so on Tizen / webOS the React frontend mounts
    # <TVBanner /> instead, driven by these fields. `tv_banner_type`
    # picks between "image" (image URL + click URL) and "html" (raw
    # snippet). Both payloads are persisted so the admin can flip modes
    # without losing data.
    tv_banner_enabled: Optional[bool] = None
    tv_banner_type: Optional[str] = None  # "image" | "html"
    tv_banner_html: Optional[str] = None
    tv_banner_image_url: Optional[str] = None
    tv_banner_click_url: Optional[str] = None
    # ── Push notifications kill-switch ──
    # Defaults to FALSE so currently-installed APKs that were built BEFORE
    # `google-services.json` was added to the project will NOT attempt to
    # call `PushNotifications.register()` (which crashes natively when
    # Firebase isn't initialised). Admin flips this ON only after shipping
    # an APK that includes the firebase config.
    push_enabled: Optional[bool] = None


SETTINGS_ID = "global"


async def _get_settings_doc(db) -> dict:
    doc = await db.settings.find_one({"id": SETTINGS_ID}, {"_id": 0})
    if not doc:
        doc = {
            "id": SETTINGS_ID,
            "whatsapp_number": "+1234567890",
            "shared_xtream_server": "",
            "shared_xtream_username": "",
            "shared_xtream_password": "",
            "shared_profiles": [],
            "external_player_enabled": False,
            "player_notice_enabled": False,
            "inline_preview_enabled": True,
            # In-app update / about defaults
            "update_enabled": False,
            "app_version": "1.3.0",
            "min_supported_version": "1.3.0",
            "update_apk_url": "",
            "update_play_url": "",
            "update_changelog": "",
            "popup_delay_seconds": 10,
            "whatsapp_message": "Hi, I need help with Nadiplayer.",
            "privacy_policy_text": (
                "Nadiplayer respects your privacy. This app is a media player that "
                "plays streams from playlists you provide. We do not collect, "
                "store, or share any personal data. Playlists are stored locally "
                "on your device only."
            ),
            "support_email": "",
            "creator_name": "",
            "creator_role": "",
            "creator_avatar_url": "",
            "creator_bio": "",
            "ads_enabled": False,
            "ads_enabled_for_users": False,
            "ads_enabled_for_guests": True,
            "ads_testing_mode": True,
            "ads_app_id": "ca-app-pub-6960839907465160~7767874649",
            "ads_banner_id": "ca-app-pub-6960839907465160/6715858872",
            "ads_interstitial_id": "ca-app-pub-6960839907465160/2856504914",
            "ads_native_id": "ca-app-pub-6960839907465160/3222873405",
            "ads_rewarded_id": "ca-app-pub-6960839907465160/7452490933",
            "ads_interstitial_every": 5,
            "terms_policy_text": "",
            "tv_banner_enabled": False,
            "tv_banner_type": "image",
            "tv_banner_html": "",
            "tv_banner_image_url": "",
            "tv_banner_click_url": "",
            "push_enabled": True,
        }
        await db.settings.insert_one(dict(doc))
    # Backfill for legacy docs
    backfill: dict = {}
    if "shared_profiles" not in doc:
        doc["shared_profiles"] = []
        backfill["shared_profiles"] = []
    if "external_player_enabled" not in doc:
        doc["external_player_enabled"] = False
        backfill["external_player_enabled"] = False
    if "player_notice_enabled" not in doc:
        doc["player_notice_enabled"] = False
        backfill["player_notice_enabled"] = False
    if "inline_preview_enabled" not in doc:
        doc["inline_preview_enabled"] = True
        backfill["inline_preview_enabled"] = True
    # Backfill the in-app update / about fields (added later)
    _defaults = {
        "update_enabled": False,
        "app_version": "1.3.0",
        "min_supported_version": "1.3.0",
        "update_apk_url": "",
        "update_play_url": "",
        "update_changelog": "",
        "popup_delay_seconds": 10,
        "whatsapp_message": "Hi, I need help with Nadiplayer.",
        "privacy_policy_text": (
            "Nadiplayer respects your privacy. This app is a media player that "
            "plays streams from playlists you provide. We do not collect, "
            "store, or share any personal data. Playlists are stored locally "
            "on your device only."
        ),
        "support_email": "",
        "creator_name": "",
        "creator_role": "",
        "creator_avatar_url": "",
        "creator_bio": "",
        "ads_enabled": False,
        "ads_enabled_for_users": False,
        "ads_enabled_for_guests": True,
        "ads_testing_mode": True,
        "ads_app_id": "ca-app-pub-6960839907465160~7767874649",
        "ads_banner_id": "",
        "ads_interstitial_id": "",
        "ads_native_id": "",
        "ads_rewarded_id": "ca-app-pub-6960839907465160/7452490933",
        "ads_interstitial_every": 5,
        # FEATURE (2026-01)
        "terms_policy_text": "",
        "tv_banner_enabled": False,
        "tv_banner_type": "image",
        "tv_banner_html": "",
        "tv_banner_image_url": "",
        "tv_banner_click_url": "",
        "push_enabled": True,
    }
    for _k, _v in _defaults.items():
        if _k not in doc:
            doc[_k] = _v
            backfill[_k] = _v
    if backfill:
        await db.settings.update_one({"id": SETTINGS_ID}, {"$set": backfill})
    return doc


# ---- User endpoints ----
@router.get("/users")
async def list_users():
    db = get_db()
    users = await db.users.find({"role": "user"}, {"_id": 0}).to_list(1000)
    return [_shape_user(u) for u in users]


@router.get("/alerts")
async def admin_alerts():
    """Return actionable alerts for the admin dashboard.

    Currently returns:
      • unconfigured_own_users: users on 'own' mode with no Xtream credentials.
    """
    db = get_db()
    users = await db.users.find({"role": "user"}, {"_id": 0}).to_list(1000)
    unconfigured = []
    for u in users:
        if u.get("xtream_mode") != "own":
            continue
        has_creds = bool(
            (u.get("xtream_server") or "").strip()
            and (u.get("xtream_username") or "").strip()
            and (u.get("xtream_password") or "").strip()
        )
        has_live = bool((u.get("live_url") or "").strip())
        has_vod = bool((u.get("vod_url") or "").strip())
        if not has_creds and not has_live and not has_vod:
            unconfigured.append({
                "id": u["id"],
                "username": u["username"],
                "created_at": u.get("created_at", ""),
                "days_remaining": _shape_user(u).get("days_remaining"),
            })
    return {"unconfigured_own_users": unconfigured}


@router.post("/users")
async def create_user(body: UserCreate):
    db = get_db()
    username = body.username.strip().lower()
    if await db.users.find_one({"username": username}):
        raise HTTPException(status_code=409, detail="Username already exists")
    now = datetime.now(timezone.utc)
    expiry = now + timedelta(days=body.days)
    doc = {
        "id": str(uuid.uuid4()),
        "username": username,
        "password_hash": hash_password(body.password),
        "role": "user",
        "created_at": now.isoformat(),
        "expiry_at": expiry.isoformat(),
        "xtream_mode": body.xtream_mode,
        "xtream_server": body.xtream_server or "",
        "xtream_username": body.xtream_username or "",
        "xtream_password": body.xtream_password or "",
        "note": body.note or "",
        # Hybrid dual-source
        "live_source_type": body.live_source_type or "",
        "live_url": body.live_url or "",
        "live_user": body.live_user or "",
        "live_pass": body.live_pass or "",
        "live_mac": body.live_mac or "",
        "vod_source_type": body.vod_source_type or "",
        "vod_url": body.vod_url or "",
        "vod_user": body.vod_user or "",
        "vod_pass": body.vod_pass or "",
        "vod_mac": body.vod_mac or "",
    }
    await db.users.insert_one(dict(doc))
    return _shape_user(doc)


@router.put("/users/{user_id}")
async def update_user(user_id: str, body: UserUpdate):
    db = get_db()
    user = await db.users.find_one({"id": user_id, "role": "user"})
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    update = {}
    if body.password is not None:
        update["password_hash"] = hash_password(body.password)
    if body.set_expiry_at is not None:
        # Basic validation
        try:
            datetime.fromisoformat(body.set_expiry_at)
        except Exception:
            raise HTTPException(status_code=400, detail="Invalid set_expiry_at")
        update["expiry_at"] = body.set_expiry_at
    if body.extend_days is not None:
        base_iso = update.get("expiry_at", user.get("expiry_at"))
        try:
            base = datetime.fromisoformat(base_iso) if base_iso else datetime.now(timezone.utc)
        except Exception:
            base = datetime.now(timezone.utc)
        if base.tzinfo is None:
            base = base.replace(tzinfo=timezone.utc)
        # If already expired, extend from now
        if base < datetime.now(timezone.utc):
            base = datetime.now(timezone.utc)
        update["expiry_at"] = (base + timedelta(days=body.extend_days)).isoformat()
    for field in ("xtream_mode", "xtream_server", "xtream_username", "xtream_password", "note",
                   "live_source_type", "live_url", "live_user", "live_pass", "live_mac",
                   "vod_source_type", "vod_url", "vod_user", "vod_pass", "vod_mac"):
        val = getattr(body, field)
        if val is not None:
            update[field] = val
    # ads_enabled is a tri-state: None (no change), True (force on), False (force off).
    # We intentionally treat False as "set", so the admin can flip the toggle off.
    if body.ads_enabled is not None:
        update["ads_enabled"] = bool(body.ads_enabled)
    if update:
        await db.users.update_one({"id": user_id}, {"$set": update})
    fresh = await db.users.find_one({"id": user_id}, {"_id": 0})
    return _shape_user(fresh)


@router.delete("/users/{user_id}")
async def delete_user(user_id: str):
    db = get_db()
    res = await db.users.delete_one({"id": user_id, "role": "user"})
    if res.deleted_count == 0:
        raise HTTPException(status_code=404, detail="User not found")
    return {"ok": True}


# ---- Settings endpoints ----
@router.get("/settings")
async def get_settings():
    db = get_db()
    return await _get_settings_doc(db)


@router.put("/settings")
async def put_settings(body: SettingsUpdate):
    db = get_db()
    await _get_settings_doc(db)  # ensure exists
    raw = body.model_dump()
    update: dict = {}
    for k, v in raw.items():
        if v is None:
            continue
        if k == "shared_profiles":
            # Normalize list, assign ids where missing
            cleaned = []
            for p in v:
                cleaned.append({
                    "id": p.get("id") or str(uuid.uuid4()),
                    "name": (p.get("name") or "").strip() or "Profile",
                    "kind": p.get("kind") or "xtream",
                    "server": (p.get("server") or "").strip(),
                    "username": (p.get("username") or "").strip(),
                    "password": p.get("password") or "",
                    "m3u_url": (p.get("m3u_url") or "").strip(),
                    "m3u_content": p.get("m3u_content") or "",
                    "portal_url": (p.get("portal_url") or "").strip(),
                    "mac_address": (p.get("mac_address") or "").strip().upper(),
                })
            update["shared_profiles"] = cleaned
        else:
            update[k] = v
    if update:
        await db.settings.update_one({"id": SETTINGS_ID}, {"$set": update})
    doc = await db.settings.find_one({"id": SETTINGS_ID}, {"_id": 0})
    return doc
