"""
Telegram Mini App webapp endpoints.

- Token generation per instance (stored in Redis, 24h TTL)
- Send mini app link to user's Telegram
- Serve the mini app HTML (token-gated, no browser access)
- WebSocket for real-time instance data (token-auth, not JWT)
"""

import os
import json
import secrets
import hashlib
import hmac
from pathlib import Path

from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect, Query, Request
from fastapi.responses import FileResponse, Response
from sqlalchemy.ext.asyncio import AsyncSession
from redis.asyncio import Redis
import httpx

from app.dependencies import get_db, get_current_user, get_redis
from app.modules.user.models import User
from app.modules.terminal.auto.service import TerminalService
from app.modules.terminal.auto.crypto import CryptoService
from app.services.redis_service import get_redis_client
from app.config import get_settings

router = APIRouter()

STATIC_DIR = Path(__file__).parent / "static"


def _webapp_token_key(instance_id: int) -> str:
    return f"webapp:token:{instance_id}"


def _telegram_config_key(user_id: int) -> str:
    return f"user:{user_id}:telegram"


def _saved_creds_key(user_id: int, platform: str) -> str:
    return f"user:{user_id}:saved_creds:{platform}"


# ============================================================================
# Saved trading credentials (long-term, encrypted in Redis)
# ============================================================================

@router.post("/credentials/save")
async def save_credentials(
    request: Request,
    current_user: User = Depends(get_current_user),
    redis: Redis = Depends(get_redis),
):
    """Save trading credentials (Kalshi API key + RSA key) for reuse."""
    body = await request.json()
    platform = body.get("platform", "kalshi")
    settings = get_settings()
    crypto = CryptoService(master_key=settings.SECRET_KEY)

    if platform == "kalshi":
        api_key = body.get("api_key", "").strip()
        rsa_key = body.get("rsa_key", "").strip()
        if not api_key or not rsa_key:
            return Response(
                json.dumps({"error": "api_key and rsa_key required"}),
                status_code=400, media_type="application/json",
            )
        # Validate api_key is UUID-like and rsa_key starts with PEM header
        import re
        if not re.match(r'^[a-f0-9\-]{36}$', api_key):
            return Response(
                json.dumps({"error": "api_key must be a valid UUID"}),
                status_code=400, media_type="application/json",
            )
        if "PRIVATE KEY" not in rsa_key:
            return Response(
                json.dumps({"error": "rsa_key must be a PEM-formatted private key"}),
                status_code=400, media_type="application/json",
            )
        await redis.set(
            _saved_creds_key(current_user.id, "kalshi"),
            json.dumps({
                "api_key": crypto.encrypt(api_key),
                "rsa_key": crypto.encrypt(rsa_key),
            }),
            ex=86400 * 30,  # 30 days
        )
    else:
        return Response(
            json.dumps({"error": f"Unsupported platform: {platform}"}),
            status_code=400, media_type="application/json",
        )

    return {"ok": True, "platform": platform}


@router.get("/credentials/check/{platform}")
async def check_saved_credentials(
    platform: str,
    current_user: User = Depends(get_current_user),
    redis: Redis = Depends(get_redis),
):
    """Check if user has saved credentials for a platform."""
    data = await redis.get(_saved_creds_key(current_user.id, platform))
    if not data:
        return {"saved": False}

    # Return masked info so user knows what's saved
    if platform == "kalshi":
        settings = get_settings()
        crypto = CryptoService(master_key=settings.SECRET_KEY)
        parsed = json.loads(data)
        api_key = crypto.decrypt(parsed["api_key"])
        return {
            "saved": True,
            "api_key_masked": api_key[:8] + "..." + api_key[-4:] if len(api_key) > 12 else "****",
        }
    return {"saved": True}


@router.post("/credentials/load/{platform}")
async def load_saved_credentials(
    platform: str,
    current_user: User = Depends(get_current_user),
    redis: Redis = Depends(get_redis),
):
    """Load saved credentials into the active session (never returns raw keys)."""
    data = await redis.get(_saved_creds_key(current_user.id, platform))
    if not data:
        return Response(
            json.dumps({"error": "No saved credentials"}),
            status_code=404, media_type="application/json",
        )

    settings = get_settings()
    crypto = CryptoService(master_key=settings.SECRET_KEY)
    parsed = json.loads(data)

    if platform == "kalshi":
        # Inject directly into the session credential store (encrypted)
        # Use user-configured TTL
        ttl_hours = getattr(current_user, "session_ttl_hours", 72) or 72
        ttl_seconds = max(1, min(96, ttl_hours)) * 3600
        session_key = f"user:{current_user.id}:credentials"
        await redis.set(
            session_key,
            json.dumps({
                "platform": "kalshi",
                "api_key": parsed["api_key"],  # Already encrypted
                "rsa_key": parsed["rsa_key"],  # Already encrypted
                "timestamp": __import__("datetime").datetime.utcnow().isoformat(),
            }),
            ex=ttl_seconds,
        )
        api_key = crypto.decrypt(parsed["api_key"])
        return {
            "platform": "kalshi",
            "loaded": True,
            "api_key_masked": api_key[:8] + "..." + api_key[-4:] if len(api_key) > 12 else "****",
        }

    return Response(
        json.dumps({"error": f"Unsupported platform: {platform}"}),
        status_code=400, media_type="application/json",
    )


@router.delete("/credentials/{platform}")
async def delete_saved_credentials(
    platform: str,
    current_user: User = Depends(get_current_user),
    redis: Redis = Depends(get_redis),
):
    """Delete saved credentials for a platform."""
    await redis.delete(_saved_creds_key(current_user.id, platform))
    return {"ok": True, "platform": platform}


# ============================================================================
# Telegram account setup (store chat_id for sending mini app links)
# ============================================================================

@router.post("/telegram/setup")
async def setup_telegram(
    request: Request,
    current_user: User = Depends(get_current_user),
    redis: Redis = Depends(get_redis),
):
    """Store user's Telegram chat ID for sending mini app links."""
    body = await request.json()
    chat_id = str(body.get("chat_id", "")).strip()
    if not chat_id or not chat_id.isdigit():
        return {"error": "Valid numeric chat_id required"}, 400

    await redis.set(
        _telegram_config_key(current_user.id),
        json.dumps({"chat_id": chat_id}),
        ex=86400 * 30,  # 30 days
    )
    return {"ok": True, "chat_id": chat_id}


@router.get("/telegram/setup")
async def get_telegram_setup(
    current_user: User = Depends(get_current_user),
    redis: Redis = Depends(get_redis),
):
    """Check if user has Telegram configured."""
    data = await redis.get(_telegram_config_key(current_user.id))
    if not data:
        return {"configured": False}
    parsed = json.loads(data)
    return {"configured": True, "chat_id": parsed["chat_id"]}


# ============================================================================
# Webapp token generation + send link via Telegram
# ============================================================================

@router.post("/instances/{instance_id}/webapp-token")
async def generate_webapp_token(
    instance_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
    redis: Redis = Depends(get_redis),
):
    """Generate a webapp token for this instance and return the URL."""
    # Verify instance belongs to user
    service = TerminalService(db, current_user.id, redis)
    await service.get_instance(instance_id)

    # Generate secure token
    token = secrets.token_urlsafe(32)

    # Store: token -> {instance_id, user_id} with 24h TTL
    await redis.set(
        _webapp_token_key(instance_id),
        json.dumps({
            "token": token,
            "instance_id": instance_id,
            "user_id": current_user.id,
        }),
        ex=86400,
    )

    return {"token": token, "instance_id": instance_id}


@router.post("/instances/{instance_id}/send-telegram")
async def send_telegram_link(
    instance_id: int,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
    redis: Redis = Depends(get_redis),
):
    """Generate webapp token and send mini app link to user's Telegram."""
    # Verify instance
    service = TerminalService(db, current_user.id, redis)
    instance = await service.get_instance(instance_id)

    # Get telegram bot from instance association
    from app.modules.terminal.auto.models import TelegramBot
    from sqlalchemy import select as sa_select
    if not instance.telegram_bot_id:
        return Response(
            json.dumps({"error": "No Telegram bot associated with this instance. Select one when deploying."}),
            status_code=400,
            media_type="application/json",
        )

    bot_result = await db.execute(
        sa_select(TelegramBot).where(
            TelegramBot.id == instance.telegram_bot_id,
            TelegramBot.user_id == current_user.id,
        )
    )
    tg_bot = bot_result.scalar_one_or_none()
    if not tg_bot:
        return Response(
            json.dumps({"error": "Telegram bot not found or not owned by you."}),
            status_code=400,
            media_type="application/json",
        )

    bot_token = tg_bot.bot_token
    chat_id = tg_bot.chat_id

    # Generate token
    token = secrets.token_urlsafe(32)
    await redis.set(
        _webapp_token_key(instance_id),
        json.dumps({
            "token": token,
            "instance_id": instance_id,
            "user_id": current_user.id,
        }),
        ex=86400,
    )

    # Build webapp URL
    webapp_base = os.environ.get("WEBAPP_URL", "").rstrip("/")
    if not webapp_base:
        # Derive from API base
        webapp_base = os.environ.get("API_BASE_URL", "https://luckyst.trade")
    webapp_url = f"{webapp_base}/api/v1/terminal/webapp/app/{instance_id}?token={token}"

    # Send via Telegram Bot API
    markets = instance.markets.get("markets", []) if instance.markets else []
    # Sanitize market label for Telegram message
    raw_label = " / ".join(m.split("-")[-1] for m in markets) if markets else f"Instance #{instance_id}"
    market_label = "".join(c for c in raw_label if c.isalnum() or c in " /-_#")

    try:
        async with httpx.AsyncClient() as client:
            resp = await client.post(
                f"https://api.telegram.org/bot{bot_token}/sendMessage",
                json={
                    "chat_id": chat_id,
                    "text": f"Trading Terminal: {market_label}",
                    "reply_markup": {
                        "inline_keyboard": [[{
                            "text": f"Open {market_label}",
                            "web_app": {"url": webapp_url},
                        }]]
                    },
                },
                timeout=10,
            )
            if resp.status_code != 200:
                return Response(
                    json.dumps({"error": f"Telegram API error: {resp.text}"}),
                    status_code=502,
                    media_type="application/json",
                )
    except Exception as e:
        return Response(
            json.dumps({"error": f"Failed to send Telegram message: {str(e)}"}),
            status_code=502,
            media_type="application/json",
        )

    return {"ok": True, "sent_to": chat_id, "instance_id": instance_id}


# ============================================================================
# Serve mini app HTML (token-gated)
# ============================================================================

@router.get("/app/{instance_id}")
async def serve_miniapp(
    instance_id: int,
    token: str = Query(default=""),
    redis: Redis = Depends(get_redis),
):
    """Serve the Telegram mini app. Only accessible with valid webapp token."""
    if not token:
        return Response("Not found", status_code=404)

    stored = await redis.get(_webapp_token_key(instance_id))
    if not stored:
        return Response("Not found", status_code=404)

    data = json.loads(stored)
    if not hmac.compare_digest(data.get("token", ""), token):
        return Response("Not found", status_code=404)

    return FileResponse(str(STATIC_DIR / "miniapp.html"))


# ============================================================================
# WebSocket for mini app (token-auth, NOT JWT)
# ============================================================================

@router.websocket("/ws/{instance_id}")
async def miniapp_websocket(
    ws: WebSocket,
    instance_id: int,
    token: str = Query(default=""),
):
    """WebSocket for mini app. Auth via webapp token, not JWT."""
    redis_client = await get_redis_client()

    # Validate token
    stored = await redis_client.get(_webapp_token_key(instance_id))
    if not stored or not token:
        await ws.close(code=1008)
        return

    data = json.loads(stored)
    if not hmac.compare_digest(data.get("token", ""), token):
        await ws.close(code=1008)
        return

    await ws.accept()

    pubsub = redis_client.pubsub()
    try:
        await pubsub.subscribe(f"trading:instance:{instance_id}:updates")

        async for message in pubsub.listen():
            if message["type"] == "message":
                await ws.send_text(message["data"])
    except WebSocketDisconnect:
        pass
    except Exception:
        pass
    finally:
        await pubsub.unsubscribe(f"trading:instance:{instance_id}:updates")
        await pubsub.close()


# ============================================================================
# Command endpoint for mini app (token-auth)
# ============================================================================

@router.post("/cmd/{instance_id}")
async def miniapp_command(
    instance_id: int,
    request: Request,
    token: str = Query(default=""),
    redis: Redis = Depends(get_redis),
):
    """Execute a trading command from the mini app. Token-auth."""
    # Validate token
    stored = await redis.get(_webapp_token_key(instance_id))
    if not stored or not token:
        return Response("Unauthorized", status_code=401)

    data = json.loads(stored)
    if not hmac.compare_digest(data.get("token", ""), token):
        return Response("Unauthorized", status_code=401)

    body = await request.json()
    action = body.get("action", "")

    command_key = f"trading:instance:{instance_id}:command"

    # Map mini app actions to Redis commands
    if action == "jump":
        market_index = body.get("market_index", 0)
        if not isinstance(market_index, int) or market_index not in (0, 1):
            return Response(json.dumps({"error": "market_index must be 0 or 1"}), status_code=400, media_type="application/json")
        await redis.rpush(command_key, json.dumps({"action": "toggle_jump", "market_index": market_index}))
        return {"ok": True, "action": "toggle_jump", "market_index": market_index}

    elif action == "toggle_pause":
        await redis.rpush(command_key, json.dumps({"action": "toggle_pause"}))
        return {"ok": True, "action": "toggle_pause"}

    elif action == "single_fire":
        await redis.rpush(command_key, json.dumps({"action": "single_fire"}))
        return {"ok": True, "action": "single_fire"}

    elif action == "stop":
        await redis.rpush(command_key, json.dumps({"action": "stop"}))
        return {"ok": True, "action": "stop"}

    elif action == "force_stop":
        await redis.rpush(command_key, json.dumps({"action": "force_stop"}))
        return {"ok": True, "action": "force_stop"}

    elif action == "cancel_orders":
        await redis.rpush(command_key, json.dumps({"action": "cancel_orders"}))
        return {"ok": True, "action": "cancel_orders"}

    elif action == "fair_value":
        await redis.rpush(command_key, json.dumps({"action": "toggle_fair_value"}))
        return {"ok": True, "action": "toggle_fair_value"}

    elif action == "bump":
        # Bump = jump on specific market (used in jump mode for bump m1/m2)
        market_index = body.get("market_index", 0)
        if not isinstance(market_index, int) or market_index not in (0, 1):
            return Response(json.dumps({"error": "market_index must be 0 or 1"}), status_code=400, media_type="application/json")
        await redis.rpush(command_key, json.dumps({"action": "toggle_jump", "market_index": market_index}))
        return {"ok": True, "action": "bump", "market_index": market_index}

    else:
        return Response(
            json.dumps({"error": f"Unknown action: {action}"}),
            status_code=400,
            media_type="application/json",
        )
