import asyncio
import html
import time
from typing import Any

import httpx

from config import cfg
import db

_client: httpx.AsyncClient | None = None
_lock = asyncio.Lock()
_counts = {"like100": 0, "like200": 0}
_total_counts = {"like100": 0, "like200": 0}
_last_jwt_file: dict[str, dict[str, str]] = {"like100": {}, "like200": {}}
_disabled: dict[str, dict[str, Any]] = {}
_tasks: dict[str, asyncio.Task] = {}
_update_offset: int | None = None
_SAFE_JWT_FIELDS = {
    "server",
    "purpose",
    "file_index",
    "file_total",
    "file_id",
    "file_name",
    "accounts",
    "success",
    "failed",
    "queue_mode",
    "next_file_index",
    "generated_at",
}


def _enabled() -> bool:
    return bool(cfg.TELEGRAM_BOT_TOKEN)


def _endpoint_cfg(endpoint: str) -> dict[str, Any]:
    settings = db.get_controller_settings(
        cfg.JWT_REFRESH_THRESHOLD_100,
        cfg.JWT_REFRESH_THRESHOLD_200,
    )
    thresholds = settings["thresholds"]
    if endpoint == "like100":
        return {
            "threshold": thresholds["like100"],
            "command": cfg.JWT_COMMAND_100,
            "success_text": cfg.JWT_SUCCESS_TEXT_100,
        }
    if endpoint == "like200":
        return {
            "threshold": thresholds["like200"],
            "command": cfg.JWT_COMMAND_200,
            "success_text": cfg.JWT_SUCCESS_TEXT_200,
        }
    return {"threshold": 0, "command": "", "success_text": ""}


async def _tg_client() -> httpx.AsyncClient:
    global _client
    if _client is None or _client.is_closed:
        _client = httpx.AsyncClient(timeout=20.0, trust_env=False)
    return _client


async def close_telegram_client():
    global _client
    if _client is not None:
        await _client.aclose()
        _client = None


async def send_message(chat_id: str, text: str):
    if not _enabled() or not chat_id:
        return
    client = await _tg_client()
    await client.post(
        f"https://api.telegram.org/bot{cfg.TELEGRAM_BOT_TOKEN}/sendMessage",
        json={
            "chat_id": chat_id,
            "text": text[:3900],
            "parse_mode": "HTML",
            "disable_web_page_preview": True,
        },
    )


def _snapshot_for(endpoint: str, threshold: int, will_refresh: bool) -> dict[str, Any]:
    return {
        "cycle_count": _counts.get(endpoint, 0),
        "threshold": threshold,
        "total_count": _total_counts.get(endpoint, 0),
        "will_refresh": will_refresh,
        "disabled": endpoint in _disabled,
        "jwt_file": dict(_last_jwt_file.get(endpoint, {})),
    }


def _requested_label(entry: dict, response: dict | None) -> str:
    endpoint = str(entry.get("endpoint", "-"))
    requested = entry.get("requested", response.get("requested") if response else None)
    if endpoint == "like100":
        return "like100 / 100 likes"
    if endpoint == "like200":
        return "like200 / 200 likes"
    if endpoint == "freelike":
        return f"freelike / {requested} likes"
    return f"{endpoint} / {requested or '-'} likes"


def _jwt_meta_from_response(response: dict | None) -> dict[str, str]:
    if not response:
        return {}
    meta = response.get("jwt_meta")
    if not isinstance(meta, dict):
        return {}
    safe_keys = {
        "server",
        "purpose",
        "file_index",
        "file_total",
        "file_id",
        "file_name",
        "file_requests",
        "total_requests",
        "success_requests",
        "failed_requests",
        "reset_day",
        "reset_time",
    }
    return {
        key: str(value)
        for key, value in meta.items()
        if key in safe_keys and value not in (None, "")
    }


def notify_like_log(
    entry: dict,
    response: dict | None = None,
    error: str | None = None,
    stats: dict | None = None,
    daily_stats: dict | None = None,
):
    if not _enabled() or not cfg.TELEGRAM_LOG_CHAT_ID:
        return
    asyncio.create_task(_notify_like_log(entry, response, error, stats, daily_stats))


async def _notify_like_log(
    entry: dict,
    response: dict | None,
    error: str | None,
    stats: dict | None,
    daily_stats: dict | None,
):
    status = "ERROR" if error else "SUCCESS"
    upstream_jwt = _jwt_meta_from_response(response)
    jwt_file = upstream_jwt or (stats or {}).get("jwt_file") or {}
    lines = [
        f"<b>AMS FF Like {status}</b>",
        f"Type: <code>{html.escape(_requested_label(entry, response))}</code>",
        f"UID: <code>{html.escape(str(entry.get('uid', '-')))}</code>",
        f"Server: <code>{html.escape(str(entry.get('server_name') or (response or {}).get('server_name') or '-'))}</code>",
        f"User: <code>{html.escape(str(entry.get('key_name') or entry.get('nick') or '-'))}</code>",
        f"Requested amount: <code>{html.escape(str(entry.get('requested', response.get('requested', '-') if response else '-')))}</code>",
        f"Sent: <code>{html.escape(str(entry.get('success', response.get('likes_sent', '-') if response else '-')))}</code>",
        f"Cut: <code>{html.escape(str(entry.get('limit_cut', response.get('limit_cut', '-') if response else '-')))}</code>",
    ]
    if response and "remaining_today" in response:
        lines.append(f"Remain: <code>{html.escape(str(response['remaining_today']))}</code>")
    if stats:
        threshold = stats.get("threshold") or 0
        if threshold:
            lines.append(
                f"Request Cycle: <code>{html.escape(str(stats.get('cycle_count', 0)))} / {html.escape(str(threshold))}</code>"
            )
        if "total_count" in stats and "total_requests" not in upstream_jwt:
            lines.append(f"Total Requests: <code>{html.escape(str(stats.get('total_count', 0)))}</code>")
    if jwt_file:
        lines.extend(
            [
                f"JWT File: <code>{html.escape(str(jwt_file.get('file_index', '-')))} / {html.escape(str(jwt_file.get('file_total', '-')))}</code>",
                f"JWT File Name: <code>{html.escape(str(jwt_file.get('file_name', '-')))}</code>",
            ]
        )
        if jwt_file.get("server"):
            lines.append(f"JWT Server: <code>{html.escape(str(jwt_file.get('server')))}</code>")
        if "file_requests" in jwt_file:
            lines.append(f"File Requests: <code>{html.escape(str(jwt_file.get('file_requests')))}</code>")
        if "total_requests" in jwt_file:
            lines.append(f"Total Requests: <code>{html.escape(str(jwt_file.get('total_requests')))}</code>")
        if "success_requests" in jwt_file:
            lines.append(f"Success Requests: <code>{html.escape(str(jwt_file.get('success_requests')))}</code>")
        if "failed_requests" in jwt_file:
            lines.append(f"Failed Requests: <code>{html.escape(str(jwt_file.get('failed_requests')))}</code>")
        if jwt_file.get("reset_day"):
            lines.append(f"Reset Day: <code>{html.escape(str(jwt_file.get('reset_day')))}</code>")
        if jwt_file.get("reset_time"):
            lines.append(f"Reset Time: <code>{html.escape(str(jwt_file.get('reset_time')))}</code>")
        if jwt_file.get("next_file_index"):
            lines.append(f"Next JWT File: <code>{html.escape(str(jwt_file.get('next_file_index')))}</code>")
        if jwt_file.get("generated_at"):
            lines.append(f"JWT Generated At: <code>{html.escape(str(jwt_file.get('generated_at')))}</code>")
    elif stats:
        lines.append("JWT File: <code>not generated yet</code>")
    if daily_stats:
        lines.extend(
            [
                "",
                f"Today Requests: <code>{html.escape(str(daily_stats.get('requests', 0)))}</code>",
                f"Today Likes Sent: <code>{html.escape(str(daily_stats.get('likes_sent', 0)))}</code>",
                f"Today Limit Cut: <code>{html.escape(str(daily_stats.get('limit_cut', 0)))}</code>",
                f"Today Failed: <code>{html.escape(str(daily_stats.get('failed_requests', 0)))}</code>",
            ]
        )
    if error:
        lines.append(f"Error: <code>{html.escape(error)}</code>")
    try:
        await send_message(cfg.TELEGRAM_LOG_CHAT_ID, "\n".join(lines))
    except Exception:
        pass


async def ensure_endpoint_available(endpoint: str):
    info = _disabled.get(endpoint)
    if not info:
        return
    raise RuntimeError(f"{endpoint} is temporarily off for JWT refresh.")


async def record_like_request(endpoint: str) -> dict[str, Any] | None:
    if endpoint not in _counts:
        return None
    async with _lock:
        _total_counts[endpoint] = _total_counts.get(endpoint, 0) + 1
        settings = _endpoint_cfg(endpoint)
        threshold = int(settings["threshold"] or 0)
        if endpoint in _disabled:
            return _snapshot_for(endpoint, threshold, False)
        if threshold <= 0:
            return _snapshot_for(endpoint, threshold, False)
        _counts[endpoint] = _counts.get(endpoint, 0) + 1
        if _counts[endpoint] < threshold:
            return _snapshot_for(endpoint, threshold, False)
        _counts[endpoint] = 0
        _disabled[endpoint] = {"since": time.time(), "reason": "jwt_refresh"}
        stats = _snapshot_for(endpoint, threshold, True)
        if endpoint not in _tasks or _tasks[endpoint].done():
            _tasks[endpoint] = asyncio.create_task(_refresh_jwt(endpoint))
        return stats


async def set_thresholds(like100: int | None = None, like200: int | None = None) -> dict[str, Any]:
    db.set_controller_thresholds(like100, like200)
    async with _lock:
        return get_controller_status()


async def reset_counts(endpoint: str | None = None) -> dict[str, Any]:
    async with _lock:
        targets = [endpoint] if endpoint in _counts else list(_counts)
        for key in targets:
            _counts[key] = 0
        return get_controller_status()


async def _refresh_jwt(endpoint: str):
    settings = _endpoint_cfg(endpoint)
    command = settings["command"]
    success_text = str(settings["success_text"] or "").lower()
    group_id = cfg.TELEGRAM_GROUP_ID or cfg.TELEGRAM_LOG_CHAT_ID
    try:
        await send_message(
            cfg.TELEGRAM_LOG_CHAT_ID or group_id,
            f"<b>{endpoint} OFF</b>\nThreshold reached. Starting JWT refresh.",
        )
        if command:
            await send_message(group_id, html.escape(command))
        if not success_text:
            return
        await _wait_for_success(endpoint, group_id, success_text)
    finally:
        _disabled.pop(endpoint, None)
        await send_message(
            cfg.TELEGRAM_LOG_CHAT_ID or group_id,
            f"<b>{endpoint} ON</b>\nJWT refresh flow completed or timed out.",
        )


async def _wait_for_success(endpoint: str, group_id: str, success_text: str):
    global _update_offset
    if not _enabled():
        return
    client = await _tg_client()
    deadline = time.time() + max(int(cfg.JWT_REFRESH_TIMEOUT_SECONDS), 30)
    while time.time() < deadline:
        try:
            params = {"timeout": int(max(cfg.TELEGRAM_POLL_SECONDS, 1))}
            if _update_offset is not None:
                params["offset"] = _update_offset
            resp = await client.get(
                f"https://api.telegram.org/bot{cfg.TELEGRAM_BOT_TOKEN}/getUpdates",
                params=params,
            )
            data = resp.json()
            for update in data.get("result", []):
                _update_offset = int(update["update_id"]) + 1
                msg = update.get("message") or update.get("edited_message") or {}
                chat_id = str((msg.get("chat") or {}).get("id", ""))
                raw_text = str(msg.get("text") or msg.get("caption") or "")
                text = raw_text.lower()
                if str(group_id) and chat_id and chat_id != str(group_id):
                    continue
                if success_text in text:
                    metadata = _parse_jwt_metadata(raw_text)
                    parsed_endpoint = metadata.get("endpoint")
                    if not parsed_endpoint or parsed_endpoint == endpoint:
                        metadata.pop("endpoint", None)
                        async with _lock:
                            _last_jwt_file[endpoint] = metadata
                    return
        except Exception:
            await asyncio.sleep(2)


def _parse_jwt_metadata(text: str) -> dict[str, str]:
    parsed: dict[str, str] = {}
    for line in text.splitlines():
        if ":" not in line:
            continue
        key, value = line.split(":", 1)
        key = key.strip().lower()
        value = value.strip()
        if key == "endpoint":
            parsed[key] = value.lower()
        elif key in _SAFE_JWT_FIELDS:
            parsed[key] = value
    return parsed


def get_controller_status() -> dict:
    settings = db.get_controller_settings(
        cfg.JWT_REFRESH_THRESHOLD_100,
        cfg.JWT_REFRESH_THRESHOLD_200,
    )
    return {
        "counts": dict(_counts),
        "total_counts": dict(_total_counts),
        "last_jwt_file": {
            "like100": dict(_last_jwt_file.get("like100", {})),
            "like200": dict(_last_jwt_file.get("like200", {})),
        },
        "disabled": {
            key: {"since": value.get("since"), "reason": value.get("reason")}
            for key, value in _disabled.items()
        },
        "thresholds": {
            "like100": settings["thresholds"]["like100"],
            "like200": settings["thresholds"]["like200"],
        },
    }
