"""
AMS FF Like — Full Stack
GET /                → User Panel  
GET /admin           → Admin Panel
GET /like100?uid=X&api_key=Y  → Like 100 (browser থেকে সরাসরি)
GET /like200?uid=X&api_key=Y  → Like 200
GET /like?uid=X&api_key=Y&type=free  → Free Like
GET /like?uid=X&api_key=Y&type=100  → Unified Like API
POST /like100        → Like 100
POST /like200        → Like 200
POST /like           → Unified Like API
GET /admin/keys      → সব keys দেখো
GET /admin/logs      → logs দেখো
POST /admin/reset    → daily reset
GET /user/me?api_key=X → নিজের info
GET /user/logs?api_key=X → নিজের logs
"""
import logging
import os
from contextlib import asynccontextmanager
from datetime import datetime
from pathlib import Path
import httpx
from fastapi import FastAPI, HTTPException, Header, Request, Query
from fastapi.responses import HTMLResponse, JSONResponse, Response
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import db
import telegram_service
from utils import call_like_api, calc_cut_100, calc_cut_200, calc_cut_free, close_http_clients, UpstreamAPIError
from config import cfg

logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
logger = logging.getLogger("ams")

BASE_DIR   = Path(__file__).parent
ADMIN_HTML = (BASE_DIR / "static" / "admin.html").read_text(encoding="utf-8")
USER_HTML  = (BASE_DIR / "static" / "user.html").read_text(encoding="utf-8")
DOCS_HTML  = (BASE_DIR / "static" / "docs.html").read_text(encoding="utf-8")

@asynccontextmanager
async def lifespan(app: FastAPI):
    yield
    await close_http_clients()
    await telegram_service.close_telegram_client()

app = FastAPI(title="AMS FF Like", version="3.0.0",
              docs_url=None, redoc_url=None, openapi_url=None,
              lifespan=lifespan)

app.add_middleware(CORSMiddleware, allow_origins=["*"],
                   allow_methods=["*"], allow_headers=["*"])

def check_admin(token: str):
    if token != cfg.ADMIN_TOKEN:
        raise HTTPException(403, "Invalid admin token.")


# ── Models ────────────────────────────────────────────────────────────────────
class LikeBody(BaseModel):
    uid: str
    api_key: str
    server_name: str | None = None

class UnifiedLikeBody(BaseModel):
    uid: str
    api_key: str
    type: str = "100"
    server_name: str | None = None

class AdminKeyCreateBody(BaseModel):
    name: str
    nick: str | None = None
    daily_limit: int = 20
    total_limit: int = 500

class AdminKeyUpdateBody(BaseModel):
    name: str | None = None
    nick: str | None = None
    daily_limit: int | None = None
    total_limit: int | None = None

class ControllerThresholdBody(BaseModel):
    like100: int | None = None
    like200: int | None = None

class ControllerResetBody(BaseModel):
    endpoint: str | None = None


# ── Pages ─────────────────────────────────────────────────────────────────────
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
async def user_panel(): return HTMLResponse(USER_HTML)

@app.get("/admin", response_class=HTMLResponse, include_in_schema=False)
async def admin_panel(): return HTMLResponse(ADMIN_HTML)

@app.get("/docs", response_class=HTMLResponse, include_in_schema=False)
async def public_docs(): return HTMLResponse(DOCS_HTML)

@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
    return Response(status_code=204)


# ── Like handler ──────────────────────────────────────────────────────────────
def normalize_like_type(value: str) -> str:
    v = str(value).strip().lower()
    aliases = {
        "100": "like100",
        "like100": "like100",
        "200": "like200",
        "like200": "like200",
        "free": "freelike",
    }
    if v not in aliases:
        raise HTTPException(400, "Invalid type. Use 100, 200, or free.")
    return aliases[v]

def get_like_config(endpoint: str):
    if endpoint == "like100":
        return cfg.LIKE_API_100, 100, calc_cut_100
    if endpoint == "like200":
        return cfg.LIKE_API_200, 200, calc_cut_200
    raise HTTPException(400, "Invalid like endpoint.")

def clean_required(value: str, field: str) -> str:
    cleaned = str(value).strip()
    if not cleaned:
        raise HTTPException(422, f"{field} is required.")
    return cleaned

def clean_server_name(value: str | None) -> str:
    cleaned = str(value or cfg.SERVER_NAME).strip().lower()
    return cleaned or cfg.SERVER_NAME

async def handle_like(uid: str, api_key: str, endpoint: str, calc_cut, server_name: str | None = None):
    uid = clean_required(uid, "uid")
    api_key = clean_required(api_key, "api_key")
    server_name = clean_server_name(server_name)
    rec = db.get_key(api_key)
    if not rec:
        raise HTTPException(401, "Invalid API key.")

    daily      = rec.get("daily_limit", 10)
    today_used = rec.get("used_today", 0)
    total_lim  = rec.get("total_limit", 300)
    total_used = rec.get("total_used", 0)
    remain     = daily - today_used

    if remain <= 0:
        raise HTTPException(429, f"Daily limit ({daily}/day) reached. Resets at 4AM BD time.")
    if total_used >= total_lim:
        raise HTTPException(429, f"Total limit ({total_lim}) reached.")

    api_url, requested, calc_cut = get_like_config(endpoint)
    expected_cut = calc_cut(requested)

    if expected_cut > remain:
        raise HTTPException(429, f"Not enough daily limit. Need {expected_cut}, remaining {remain}.")
    if total_used + expected_cut > total_lim:
        raise HTTPException(429, f"Not enough total limit. Need {expected_cut}, remaining {total_lim - total_used}.")

    try:
        await telegram_service.ensure_endpoint_available(endpoint)
    except RuntimeError as e:
        entry = {"api_key": api_key, "uid": uid, "endpoint": endpoint,
                 "requested": requested, "success": 0, "limit_cut": 0, "server_name": server_name,
                 "key_name": rec.get("name"), "error": str(e)}
        db.write_log(entry)
        daily_stats = db.get_today_stats()
        telegram_service.notify_like_log(entry, error=str(e), daily_stats=daily_stats)
        raise HTTPException(503, str(e))

    try:
        result = await call_like_api(api_url, uid, server_name=server_name)
    except UpstreamAPIError as e:
        entry = {"api_key": api_key, "uid": uid, "endpoint": endpoint,
                 "requested": requested, "success": 0, "limit_cut": 0, "server_name": server_name,
                 "key_name": rec.get("name"), "error": str(e)}
        db.write_log(entry)
        daily_stats = db.get_today_stats()
        stats = await telegram_service.record_like_request(endpoint)
        error_response = {"jwt_meta": e.jwt_meta} if e.jwt_meta else None
        telegram_service.notify_like_log(entry, response=error_response, error=str(e), stats=stats, daily_stats=daily_stats)
        raise HTTPException(502, f"Upstream like API error: {e}")
    except httpx.HTTPStatusError as e:
        entry = {"api_key": api_key, "uid": uid, "endpoint": endpoint,
                 "requested": requested, "success": 0, "limit_cut": 0, "server_name": server_name,
                 "key_name": rec.get("name"), "error": f"upstream {e.response.status_code}"}
        db.write_log(entry)
        daily_stats = db.get_today_stats()
        stats = await telegram_service.record_like_request(endpoint)
        telegram_service.notify_like_log(entry, error=entry["error"], stats=stats, daily_stats=daily_stats)
        raise HTTPException(502, f"Like API returned error {e.response.status_code}")
    except httpx.RequestError as e:
        entry = {"api_key": api_key, "uid": uid, "endpoint": endpoint,
                 "requested": requested, "success": 0, "limit_cut": 0, "server_name": server_name,
                 "key_name": rec.get("name"), "error": "network"}
        db.write_log(entry)
        daily_stats = db.get_today_stats()
        stats = await telegram_service.record_like_request(endpoint)
        telegram_service.notify_like_log(entry, error="network", stats=stats, daily_stats=daily_stats)
        raise HTTPException(502, "Cannot reach like API. Try again.")

    success = result["success"]
    cut     = calc_cut(success)

    if cut > 0:
        db.increment_usage(api_key, cut)
        remain -= cut

    entry = {"api_key": api_key, "uid": uid, "endpoint": endpoint,
             "requested": requested, "success": success,
             "limit_cut": cut, "server_name": server_name, "key_name": rec.get("name"),
             "nick": rec.get("nick")}
    db.write_log(entry)
    daily_stats = db.get_today_stats()

    response = {
        "status":          "success",
        "endpoint":        endpoint,
        "uid":             uid,
        "requested":       requested,
        "likes_sent":      success,
        "limit_cut":       cut,
        "remaining_today": max(remain, 0),
        "key_name":        rec.get("name"),
        "server_name":     server_name,
    }
    if result.get("jwt_meta"):
        response["jwt_meta"] = result["jwt_meta"]
    stats = await telegram_service.record_like_request(endpoint)
    telegram_service.notify_like_log(entry, response=response, stats=stats, daily_stats=daily_stats)
    return response


async def handle_unified_like(uid: str, api_key: str | None, like_type: str, server_name: str | None = None):
    endpoint = normalize_like_type(like_type)
    if endpoint == "freelike":
        return await handle_free_like(uid, server_name, api_key)
    if not api_key:
        raise HTTPException(422, "api_key is required.")
    _, _, calc_cut = get_like_config(endpoint)
    return await handle_like(uid, api_key, endpoint, calc_cut, server_name)


async def handle_free_like(uid: str, server_name: str | None = None, api_key: str | None = None):
    uid = clean_required(uid, "uid")
    server_name = clean_server_name(server_name)
    if not api_key:
        raise HTTPException(422, "api_key is required.")
    rec = db.get_key(api_key)
    if not rec:
        raise HTTPException(401, "Invalid API key.")
    log_api_key = api_key
    key_name = rec.get("name")
    nick = rec.get("nick")
    endpoint = "freelike"
    requested = cfg.FREE_LIKE_AMOUNT

    if requested <= 0:
        raise HTTPException(500, "Free like amount is not configured correctly.")

    try:
        result = await call_like_api(cfg.LIKE_API_FREE, uid, requested, server_name)
    except UpstreamAPIError as e:
        entry = {"api_key": log_api_key, "uid": uid, "endpoint": endpoint,
                 "requested": requested, "success": 0, "limit_cut": 0, "server_name": server_name,
                 "key_name": key_name, "nick": nick, "error": str(e)}
        db.write_log(entry)
        daily_stats = db.get_today_stats()
        error_response = {"jwt_meta": e.jwt_meta} if e.jwt_meta else None
        telegram_service.notify_like_log(entry, response=error_response, error=str(e), daily_stats=daily_stats)
        raise HTTPException(502, f"Upstream like API error: {e}")
    except httpx.HTTPStatusError as e:
        entry = {"api_key": log_api_key, "uid": uid, "endpoint": endpoint,
                 "requested": requested, "success": 0, "limit_cut": 0, "server_name": server_name,
                 "key_name": key_name, "nick": nick, "error": f"upstream {e.response.status_code}"}
        db.write_log(entry)
        daily_stats = db.get_today_stats()
        telegram_service.notify_like_log(entry, error=entry["error"], daily_stats=daily_stats)
        raise HTTPException(502, f"Like API returned error {e.response.status_code}")
    except httpx.RequestError:
        entry = {"api_key": log_api_key, "uid": uid, "endpoint": endpoint,
                 "requested": requested, "success": 0, "limit_cut": 0, "server_name": server_name,
                 "key_name": key_name, "nick": nick, "error": "network"}
        db.write_log(entry)
        daily_stats = db.get_today_stats()
        telegram_service.notify_like_log(entry, error="network", daily_stats=daily_stats)
        raise HTTPException(502, "Cannot reach like API. Try again.")

    success = result["success"]
    cut = calc_cut_free(success)

    entry = {"api_key": log_api_key, "uid": uid, "endpoint": endpoint,
             "requested": requested, "success": success,
             "limit_cut": cut, "server_name": server_name, "key_name": key_name,
             "nick": nick}
    db.write_log(entry)
    daily_stats = db.get_today_stats()

    response = {
        "status":     "success",
        "endpoint":   endpoint,
        "uid":        uid,
        "requested":  requested,
        "likes_sent": success,
        "limit_cut":  cut,
        "key_name":   key_name,
        "server_name": server_name,
    }
    if result.get("jwt_meta"):
        response["jwt_meta"] = result["jwt_meta"]
    telegram_service.notify_like_log(entry, response=response, daily_stats=daily_stats)
    return response


# ── Unified Like API ──────────────────────────────────────────────────────────
@app.get("/like", tags=["Like API"])
async def like_get(uid: str = Query(...),
                   type: str = Query("100"),
                   api_key: str = Query(...),
                   server_name: str | None = Query(None)):
    """Browser: /like?uid=123456&api_key=your_key&type=100&server_name=bd"""
    return await handle_unified_like(uid, api_key, type, server_name)

@app.post("/like", tags=["Like API"])
async def like_post(body: UnifiedLikeBody):
    return await handle_unified_like(body.uid, body.api_key, body.type, body.server_name)


# ── Like 100 ──────────────────────────────────────────────────────────────────
@app.get("/like100", tags=["Like API"])
async def like100_get(uid: str = Query(...), api_key: str = Query(...), server_name: str | None = Query(None)):
    """Browser: /like100?uid=123456&api_key=your_key&server_name=bd"""
    return await handle_like(uid, api_key, "like100", calc_cut_100, server_name)

@app.post("/like100", tags=["Like API"])
async def like100_post(body: LikeBody):
    return await handle_like(body.uid, body.api_key, "like100", calc_cut_100, body.server_name)


# ── Like 200 ──────────────────────────────────────────────────────────────────
@app.get("/like200", tags=["Like API"])
async def like200_get(uid: str = Query(...), api_key: str = Query(...), server_name: str | None = Query(None)):
    """Browser: /like200?uid=123456&api_key=your_key&server_name=bd"""
    return await handle_like(uid, api_key, "like200", calc_cut_200, server_name)

@app.post("/like200", tags=["Like API"])
async def like200_post(body: LikeBody):
    return await handle_like(body.uid, body.api_key, "like200", calc_cut_200, body.server_name)


# ── Free Like ─────────────────────────────────────────────────────────────────
# ── User endpoints ────────────────────────────────────────────────────────────
@app.get("/user/me", tags=["User"])
async def user_me(api_key: str = Query(...)):
    """নিজের info দেখো: /user/me?api_key=your_key"""
    rec = db.get_key(api_key)
    if not rec:
        raise HTTPException(401, "Invalid API key.")
    return {
        "name":            rec.get("name"),
        "nick":            rec.get("nick"),
        "daily_limit":     rec.get("daily_limit"),
        "used_today":      rec.get("used_today", 0),
        "total_limit":     rec.get("total_limit"),
        "total_used":      rec.get("total_used", 0),
        "remaining_today": rec.get("daily_limit", 0) - rec.get("used_today", 0),
        "is_active":       True,
    }

@app.get("/user/logs", tags=["User"])
async def user_logs(api_key: str = Query(...), limit: int = 20):
    """নিজের logs দেখো: /user/logs?api_key=your_key"""
    rec = db.get_key(api_key)
    if not rec:
        raise HTTPException(401, "Invalid API key.")
    return {"logs": db.get_logs_by_key(api_key, limit)}


# ── Admin endpoints ───────────────────────────────────────────────────────────
@app.get("/admin/keys", tags=["Admin"])
async def admin_keys(x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    return {"keys": db.get_all_keys()}

@app.post("/admin/keys", tags=["Admin"])
async def admin_create_key(body: AdminKeyCreateBody, x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    key = db.create_key(
        name=body.name,
        nick=body.nick or body.name,
        daily_limit=body.daily_limit,
        total_limit=body.total_limit,
    )
    return {"status": "created", "key": key}

@app.patch("/admin/keys/{api_key}", tags=["Admin"])
async def admin_update_key(api_key: str, body: AdminKeyUpdateBody, x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    key = db.update_key(api_key, body.model_dump(exclude_unset=True))
    if not key:
        raise HTTPException(404, "API key not found.")
    return {"status": "updated", "key": key}

@app.delete("/admin/keys/{api_key}", tags=["Admin"])
async def admin_delete_key(api_key: str, x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    ok = db.delete_key(api_key)
    if not ok:
        raise HTTPException(404, "API key not found.")
    return {"status": "deleted", "api_key": api_key}

@app.get("/admin/logs", tags=["Admin"])
async def admin_logs(limit: int = 50, x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    return {"logs": db.get_logs(limit), "today_stats": db.get_today_stats()}

@app.get("/admin/config", tags=["Admin"])
async def admin_config(x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    return {
        "unified_endpoint": "/like",
        "legacy_endpoints": ["/like100", "/like200"],
        "free_like_amount": cfg.FREE_LIKE_AMOUNT,
        "timeout_seconds": cfg.LIKE_API_TIMEOUT,
        "cut_rules": {
            "like100": "0 if sent < 70, else 1",
            "like200": "0 if sent < 90, 1 if sent < 120, else 2",
            "freelike": "0",
        },
        "telegram_controller": telegram_service.get_controller_status(),
    }

@app.get("/admin/controller", tags=["Admin"])
async def admin_controller(x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    return telegram_service.get_controller_status()

@app.patch("/admin/controller", tags=["Admin"])
async def admin_update_controller(body: ControllerThresholdBody, x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    if body.like100 is not None and body.like100 < 0:
        raise HTTPException(400, "like100 threshold must be 0 or higher.")
    if body.like200 is not None and body.like200 < 0:
        raise HTTPException(400, "like200 threshold must be 0 or higher.")
    return await telegram_service.set_thresholds(body.like100, body.like200)

@app.post("/admin/controller/reset-counts", tags=["Admin"])
async def admin_reset_controller_counts(body: ControllerResetBody, x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    endpoint = None
    if body.endpoint:
        endpoint = normalize_like_type(body.endpoint)
        if endpoint not in {"like100", "like200"}:
            raise HTTPException(400, "Use like100, like200, 100, 200, or leave empty.")
    return await telegram_service.reset_counts(endpoint)

@app.post("/admin/reset", tags=["Admin"])
async def admin_reset(x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    db.reset_daily_all()
    return {"status": "daily usage reset for all keys"}

@app.post("/admin/reset/{api_key}", tags=["Admin"])
async def admin_reset_key(api_key: str, x_admin_token: str = Header(...)):
    check_admin(x_admin_token)
    db.reset_key_usage(api_key)
    return {"status": f"usage reset for {api_key}"}


# ── Health ────────────────────────────────────────────────────────────────────
@app.get("/health", tags=["Health"])
async def health():
    return {
        "status":    "ok",
        "time":      datetime.utcnow().isoformat(),
        "total_keys": len(db.get_all_keys()),
    }


@app.exception_handler(Exception)
async def global_err(req: Request, exc: Exception):
    logger.error(f"Error: {exc}")
    return JSONResponse({"status": "error", "detail": str(exc)}, status_code=500)


if __name__ == "__main__":
    import uvicorn

    port = int(os.getenv("PORT", "8001"))
    uvicorn.run("main:app", host="127.0.0.1", port=port, reload=True)
