import asyncio
import binascii
import hashlib
import json
import logging
import os
from datetime import datetime, time as datetime_time, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, List, NamedTuple, Optional, Tuple

import aiohttp
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from fastapi import FastAPI, File, Form, Header, HTTPException, Query, UploadFile
from google.protobuf.json_format import MessageToJson
from google.protobuf.message import DecodeError

import like_count_pb2
import like_pb2
import uid_generator_pb2

logger = logging.getLogger("like_ob53")
app = FastAPI(title="LIKE OB53 API")

UPLOAD_TOKEN = os.getenv(
    "UPLOAD_TOKEN",
    "d2f9b7c4a1e8468f9b3d6a0c5e7f2a91c8b4e6d0a7f9452b8c1e3f6a9d0b4c7e",
)
APP_DIR = Path(__file__).resolve().parent
JWT_UPLOAD_DIR = Path(os.getenv("JWT_UPLOAD_DIR", str(APP_DIR / "jwt_uploads"))).resolve()
DATA_DIR = Path(os.getenv("LIKE_OB53_DATA_DIR", str(APP_DIR / "data"))).resolve()
TOKEN_UID_USAGE_PATH = DATA_DIR / "token_uid_usage.json"
ALLOWED_FOLDERS = {"100like", "200like"}
PURPOSE_TO_FOLDER = {"free": "100like", "like100": "100like", "like200": "200like"}
ALLOWED_SERVERS = {"bd", "ind", "sg", "my", "pk", "vn", "th", "me", "br", "us", "sac", "na"}
LEGACY_TOKEN_FILES = {
    "IND": "token_ind.json",
    "BR": "token_br.json",
    "US": "token_br.json",
    "SAC": "token_br.json",
    "NA": "token_br.json",
}
DEFAULT_LIKE_REQUEST_COUNT = int(os.getenv("DEFAULT_LIKE_REQUEST_COUNT", "20"))
HTTP_TIMEOUT_SECONDS = float(os.getenv("HTTP_TIMEOUT_SECONDS", "15"))
JWT_META_FIELDS = {"server", "purpose", "file_index", "file_total", "file_name"}
BD_TZ = timezone(timedelta(hours=6))
RESET_TIME_TEXT = "04:00 Asia/Dhaka"


class TokenBundle(NamedTuple):
    tokens: List[Dict[str, Any]]
    meta: Dict[str, Any]


_counter_lock = asyncio.Lock()
_usage_lock = asyncio.Lock()
_request_counters = {
    "total_requests": 0,
    "success_requests": 0,
    "failed_requests": 0,
    "per_file_requests": {},
    "current_file": None,
    "current_file_index": None,
    "current_file_total": None,
}


def current_bd_reset_day(now: Optional[datetime] = None) -> str:
    if now is None:
        bd_now = datetime.now(BD_TZ)
    elif now.tzinfo is None:
        bd_now = now.replace(tzinfo=BD_TZ)
    else:
        bd_now = now.astimezone(BD_TZ)
    reset_date = bd_now.date()
    if bd_now.time() < datetime_time(4, 0):
        reset_date -= timedelta(days=1)
    return reset_date.isoformat()


def previous_reset_day(reset_day: str) -> str:
    try:
        day = datetime.strptime(reset_day, "%Y-%m-%d").date()
    except ValueError:
        return reset_day
    return (day - timedelta(days=1)).isoformat()


def normalize_server(server_name: str) -> Optional[str]:
    server = str(server_name or "").strip().lower()
    if server not in ALLOWED_SERVERS:
        return None
    return server


def validate_token_payload(tokens: Any) -> Tuple[bool, Optional[str]]:
    if not isinstance(tokens, list) or not tokens:
        return False, "token file must be a non-empty JSON list"
    for index, item in enumerate(tokens):
        if not isinstance(item, dict):
            return False, f"token row {index} must be an object"
        token = item.get("token")
        if not isinstance(token, str) or not token.strip():
            return False, f"token row {index} is missing token"
    return True, None


def safe_int(value: Any, default: int) -> int:
    try:
        number = int(value)
    except (TypeError, ValueError):
        return default
    return number if number > 0 else default


def token_hash(token: str) -> str:
    return hashlib.sha256(token.encode("utf-8")).hexdigest()[:16]


def load_usage_data(reset_day: str) -> Dict[str, Any]:
    if not TOKEN_UID_USAGE_PATH.exists():
        return {}
    try:
        with TOKEN_UID_USAGE_PATH.open("r", encoding="utf-8") as file_obj:
            data = json.load(file_obj)
    except Exception:
        return {}
    if not isinstance(data, dict):
        return {}
    keep_days = {reset_day, previous_reset_day(reset_day)}
    return {day: value for day, value in data.items() if day in keep_days and isinstance(value, dict)}


def save_usage_data(data: Dict[str, Any]) -> None:
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    tmp_path = TOKEN_UID_USAGE_PATH.with_name(f"{TOKEN_UID_USAGE_PATH.name}.{os.getpid()}.tmp")
    with tmp_path.open("w", encoding="utf-8") as file_obj:
        json.dump(data, file_obj, ensure_ascii=False, separators=(",", ":"))
    try:
        os.replace(str(tmp_path), str(TOKEN_UID_USAGE_PATH))
    except PermissionError:
        with TOKEN_UID_USAGE_PATH.open("w", encoding="utf-8") as file_obj:
            json.dump(data, file_obj, ensure_ascii=False, separators=(",", ":"))
        try:
            tmp_path.unlink()
        except OSError:
            pass


def token_meta_path(token_path: Path) -> Path:
    return token_path.with_name(f"{token_path.stem}.meta.json")


def read_stored_token_meta(token_path: Path) -> Dict[str, Any]:
    meta_path = token_meta_path(token_path)
    if not meta_path.exists():
        return {}
    try:
        with meta_path.open("r", encoding="utf-8") as file_obj:
            data = json.load(file_obj)
    except Exception:
        return {}
    if not isinstance(data, dict):
        return {}
    return {key: data[key] for key in JWT_META_FIELDS if key in data}


def write_stored_token_meta(token_path: Path, meta: Dict[str, Any]) -> None:
    safe_meta = {key: meta[key] for key in JWT_META_FIELDS if key in meta}
    with token_meta_path(token_path).open("w", encoding="utf-8") as file_obj:
        json.dump(safe_meta, file_obj, ensure_ascii=False, separators=(",", ":"))


def upload_filename(filename: Optional[str]) -> Optional[str]:
    if not filename:
        return None
    name = Path(filename).name.strip()
    return name or None


def build_token_meta(token_path: Path, server: str, purpose: str, uploaded: bool) -> Dict[str, Any]:
    stored_meta = read_stored_token_meta(token_path)
    folder = token_path.parent
    token_files = sorted(folder.glob("token_*.json")) if folder.exists() else []
    file_total = len(token_files) or 1
    try:
        file_index = token_files.index(token_path) + 1
    except ValueError:
        file_index = 1

    meta = {
        "server": normalize_server(server) or str(server).lower(),
        "purpose": purpose,
        "file_index": file_index,
        "file_total": file_total,
        "file_name": token_path.name,
    }
    meta.update(stored_meta)
    meta["file_index"] = safe_int(meta.get("file_index"), file_index)
    meta["file_total"] = safe_int(meta.get("file_total"), file_total)
    if not meta.get("file_name"):
        meta["file_name"] = token_path.name
    if not uploaded:
        meta["file_index"] = 1
        meta["file_total"] = 1
    return meta


def build_fallback_meta(server: str, purpose: str) -> Dict[str, Any]:
    return {
        "server": normalize_server(server) or str(server).lower(),
        "purpose": purpose,
        "file_index": 1,
        "file_total": 1,
        "file_name": "unknown",
    }


async def mark_request_started(jwt_meta: Dict[str, Any]) -> Dict[str, Any]:
    file_name = str(jwt_meta.get("file_name") or "unknown")
    async with _counter_lock:
        _request_counters["total_requests"] += 1
        per_file = _request_counters["per_file_requests"]
        per_file[file_name] = per_file.get(file_name, 0) + 1
        _request_counters["current_file"] = file_name
        _request_counters["current_file_index"] = jwt_meta.get("file_index")
        _request_counters["current_file_total"] = jwt_meta.get("file_total")
        meta = dict(jwt_meta)
        meta.update(
            {
                "file_requests": per_file[file_name],
                "total_requests": _request_counters["total_requests"],
                "success_requests": _request_counters["success_requests"],
                "failed_requests": _request_counters["failed_requests"],
                "reset_day": current_bd_reset_day(),
                "reset_time": RESET_TIME_TEXT,
            }
        )
        return meta


async def mark_request_finished(jwt_meta: Dict[str, Any], success: bool) -> Dict[str, Any]:
    async with _counter_lock:
        if success:
            _request_counters["success_requests"] += 1
        else:
            _request_counters["failed_requests"] += 1
        meta = dict(jwt_meta)
        meta.update(
            {
                "success_requests": _request_counters["success_requests"],
                "failed_requests": _request_counters["failed_requests"],
                "total_requests": _request_counters["total_requests"],
                "reset_day": meta.get("reset_day") or current_bd_reset_day(),
                "reset_time": RESET_TIME_TEXT,
            }
        )
        return meta


async def counter_status() -> Dict[str, Any]:
    reset_day = current_bd_reset_day()
    async with _counter_lock:
        return {
            "total_requests": _request_counters["total_requests"],
            "success_requests": _request_counters["success_requests"],
            "failed_requests": _request_counters["failed_requests"],
            "reset_day": reset_day,
            "reset_time": RESET_TIME_TEXT,
            "jwt_files": {
                "current_file": _request_counters["current_file"],
                "file_index": _request_counters["current_file_index"],
                "file_total": _request_counters["current_file_total"],
            },
            "per_file_requests": dict(_request_counters["per_file_requests"]),
        }


async def select_available_tokens(uid: str, tokens: List[Dict[str, Any]], request_count: int) -> List[Dict[str, Any]]:
    reset_day = current_bd_reset_day()
    async with _usage_lock:
        usage = load_usage_data(reset_day)
        day_usage = usage.setdefault(reset_day, {})
        uid_usage = day_usage.setdefault(str(uid), {})
        if not isinstance(uid_usage, dict):
            uid_usage = {}
            day_usage[str(uid)] = uid_usage

        selected: List[Dict[str, Any]] = []
        for token_row in tokens:
            token = str(token_row.get("token") or "")
            if not token:
                continue
            safe_hash = token_hash(token)
            if uid_usage.get(safe_hash):
                continue
            selected.append(token_row)
            uid_usage[safe_hash] = True
            if len(selected) >= request_count:
                break

        if selected:
            save_usage_data(usage)
        return selected


def atomic_write_json(path: Path, tokens: List[Dict[str, Any]]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    tmp_path = path.with_name(f"{path.name}.{os.getpid()}.tmp")
    with tmp_path.open("w", encoding="utf-8") as file_obj:
        json.dump(tokens, file_obj, ensure_ascii=False, separators=(",", ":"))
    try:
        os.replace(str(tmp_path), str(path))
    except PermissionError:
        with path.open("w", encoding="utf-8") as file_obj:
            json.dump(tokens, file_obj, ensure_ascii=False, separators=(",", ":"))
        try:
            tmp_path.unlink()
        except OSError:
            pass


def load_tokens(server_name: str, purpose: str = "like100") -> Optional[TokenBundle]:
    try:
        normalized_server = normalize_server(server_name)
        if normalized_server is None:
            logger.error("Invalid server name: %s", server_name)
            return None

        upload_folder = PURPOSE_TO_FOLDER.get(purpose, "100like")
        upload_path = JWT_UPLOAD_DIR / upload_folder / f"token_{normalized_server}.json"
        if upload_path.exists():
            with upload_path.open("r", encoding="utf-8") as file_obj:
                tokens = json.load(file_obj)
            is_valid, error = validate_token_payload(tokens)
            if not is_valid:
                logger.error("Uploaded token file is invalid: %s: %s", upload_path, error)
                return None
            return TokenBundle(tokens=tokens, meta=build_token_meta(upload_path, normalized_server, purpose, True))

        legacy_name = LEGACY_TOKEN_FILES.get(str(server_name).upper(), "token_bd.json")
        legacy_path = APP_DIR / legacy_name
        with legacy_path.open("r", encoding="utf-8") as file_obj:
            tokens = json.load(file_obj)
        is_valid, error = validate_token_payload(tokens)
        if not is_valid:
            logger.error("Legacy token file is invalid: %s: %s", legacy_name, error)
            return None
        return TokenBundle(tokens=tokens, meta=build_token_meta(legacy_path, normalized_server, purpose, False))
    except Exception as exc:
        logger.error("Error loading tokens for server %s: %s", server_name, exc)
        return None


def freefire_base_url(server_name: str) -> str:
    if server_name == "IND":
        return "https://client.ind.freefiremobile.com"
    if server_name in {"BR", "US", "SAC", "NA"}:
        return "https://client.us.freefiremobile.com"
    return "https://clientbp.ggpolarbear.com"


def build_headers(token: str) -> Dict[str, str]:
    return {
        "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; ASUS_Z01QD Build/PI)",
        "Connection": "Keep-Alive",
        "Accept-Encoding": "gzip",
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/x-www-form-urlencoded",
        "Expect": "100-continue",
        "X-Unity-Version": "2018.4.11f1",
        "X-GA": "v1 1",
        "ReleaseVersion": "OB53",
    }


def encrypt_message(plaintext: bytes) -> Optional[str]:
    try:
        key = b"Yg&tc%DEuh6%Zc^8"
        iv = b"6oyZDr22E3ychjM%"
        cipher = AES.new(key, AES.MODE_CBC, iv)
        encrypted_message = cipher.encrypt(pad(plaintext, AES.block_size))
        return binascii.hexlify(encrypted_message).decode("utf-8")
    except Exception as exc:
        logger.error("Error encrypting message: %s", exc)
        return None


def create_like_protobuf(user_id: str, region: str) -> Optional[bytes]:
    try:
        message = like_pb2.like()
        message.uid = int(user_id)
        message.region = region
        if hasattr(message, "ob_version"):
            message.ob_version = "OB53"
        return message.SerializeToString()
    except Exception as exc:
        logger.error("Error creating like protobuf message: %s", exc)
        return None


def create_uid_protobuf(uid: str) -> Optional[bytes]:
    try:
        message = uid_generator_pb2.uid_generator()
        message.saturn_ = int(uid)
        message.garena = 1
        if hasattr(message, "ob_version"):
            message.ob_version = "OB53"
        return message.SerializeToString()
    except Exception as exc:
        logger.error("Error creating uid protobuf: %s", exc)
        return None


def decode_protobuf(binary: bytes) -> Optional[Any]:
    try:
        items = like_count_pb2.Info()
        items.ParseFromString(binary)
        return items
    except DecodeError as exc:
        logger.error("Error decoding Protobuf data: %s", exc)
        return None
    except Exception as exc:
        logger.error("Unexpected error during protobuf decoding: %s", exc)
        return None


async def post_bytes(
    session: aiohttp.ClientSession,
    url: str,
    encrypted_hex: str,
    token: str,
    expect_binary: bool = False,
) -> Optional[Any]:
    try:
        async with session.post(url, data=bytes.fromhex(encrypted_hex), headers=build_headers(token), ssl=False) as response:
            if response.status != 200:
                text = await response.text()
                logger.error("Request failed with status %s: %s", response.status, text)
                return None
            if expect_binary:
                return await response.read()
            return await response.text()
    except Exception as exc:
        logger.error("Exception in upstream request: %s", exc)
        return None


async def get_player_info(
    session: aiohttp.ClientSession,
    encrypted_uid: str,
    server_name: str,
    token: str,
) -> Optional[Dict[str, Any]]:
    binary = await post_bytes(
        session,
        f"{freefire_base_url(server_name)}/GetPlayerPersonalShow",
        encrypted_uid,
        token,
        expect_binary=True,
    )
    if binary is None:
        return None

    decoded = decode_protobuf(binary)
    if decoded is None:
        return None

    try:
        return json.loads(MessageToJson(decoded))
    except Exception as exc:
        logger.error("Error converting protobuf to JSON: %s", exc)
        return None


async def send_like_requests(
    session: aiohttp.ClientSession,
    uid: str,
    server_name: str,
    tokens: List[Dict[str, Any]],
    request_count: int,
) -> Optional[List[Any]]:
    protobuf_message = create_like_protobuf(uid, server_name)
    if protobuf_message is None:
        return None

    encrypted_uid = encrypt_message(protobuf_message)
    if encrypted_uid is None:
        return None

    url = f"{freefire_base_url(server_name)}/LikeProfile"
    tasks = [
        post_bytes(session, url, encrypted_uid, tokens[index % len(tokens)]["token"])
        for index in range(request_count)
    ]
    return await asyncio.gather(*tasks, return_exceptions=True)


def parse_likes(data: Dict[str, Any]) -> int:
    try:
        return int(data.get("AccountInfo", {}).get("Likes", 0))
    except Exception:
        return 0


async def process_like(uid: str, server_name: str, purpose: str, request_count: int) -> Dict[str, Any]:
    if request_count < 1:
        jwt_meta = await mark_request_started(build_fallback_meta(server_name, purpose))
        jwt_meta = await mark_request_finished(jwt_meta, False)
        return {"status": "error", "message": "count must be at least 1", "uid": uid, "jwt_meta": jwt_meta}

    token_bundle = load_tokens(server_name, purpose)
    if token_bundle is None:
        jwt_meta = await mark_request_started(build_fallback_meta(server_name, purpose))
        jwt_meta = await mark_request_finished(jwt_meta, False)
        return {"status": "error", "message": "failed to load tokens", "uid": uid, "jwt_meta": jwt_meta}

    tokens = token_bundle.tokens
    jwt_meta = await mark_request_started(token_bundle.meta)

    uid_protobuf = create_uid_protobuf(uid)
    if uid_protobuf is None:
        jwt_meta = await mark_request_finished(jwt_meta, False)
        return {"status": "error", "message": "invalid uid", "uid": uid, "jwt_meta": jwt_meta}

    encrypted_uid = encrypt_message(uid_protobuf)
    if encrypted_uid is None:
        jwt_meta = await mark_request_finished(jwt_meta, False)
        return {"status": "error", "message": "invalid uid", "uid": uid, "jwt_meta": jwt_meta}

    selected_tokens = await select_available_tokens(uid, tokens, request_count)
    if not selected_tokens:
        jwt_meta = await mark_request_finished(jwt_meta, False)
        return {
            "status": "error",
            "message": "no available accounts for this uid until next BD 4AM reset",
            "uid": uid,
            "jwt_meta": jwt_meta,
        }

    timeout = aiohttp.ClientTimeout(total=HTTP_TIMEOUT_SECONDS)
    connector = aiohttp.TCPConnector(limit=max(len(selected_tokens), 20), ttl_dns_cache=300, ssl=False)
    async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
        before = await get_player_info(session, encrypted_uid, server_name, selected_tokens[0]["token"])
        if before is None:
            jwt_meta = await mark_request_finished(jwt_meta, False)
            return {
                "status": "error",
                "message": "failed to retrieve initial player info",
                "uid": uid,
                "jwt_meta": jwt_meta,
            }

        before_like = parse_likes(before)
        like_results = await send_like_requests(session, uid, server_name, selected_tokens, len(selected_tokens))
        if like_results is None:
            jwt_meta = await mark_request_finished(jwt_meta, False)
            return {
                "status": "error",
                "message": "failed to send like requests",
                "uid": uid,
                "jwt_meta": jwt_meta,
            }

        after = await get_player_info(session, encrypted_uid, server_name, selected_tokens[0]["token"])
        if after is None:
            jwt_meta = await mark_request_finished(jwt_meta, False)
            return {
                "status": "error",
                "message": "failed to retrieve player info after like requests",
                "uid": uid,
                "jwt_meta": jwt_meta,
            }

    account_info = after.get("AccountInfo", {})
    after_like = parse_likes(after)
    like_given = after_like - before_like
    jwt_meta = await mark_request_finished(jwt_meta, True)
    return {
        "LikesGivenByAPI": like_given,
        "sent": like_given,
        "uid": uid,
        "LikesafterCommand": after_like,
        "LikesbeforeCommand": before_like,
        "PlayerNickname": str(account_info.get("PlayerNickname", "")),
        "UID": int(account_info.get("UID", 0) or 0),
        "RequestedLikes": request_count,
        "status": 1 if like_given != 0 else 2,
        "response_status": "success",
        "jwt_meta": jwt_meta,
    }


@app.get("/")
async def root() -> Dict[str, str]:
    return {"status": "ok", "service": "LIKE OB53 API"}


@app.get("/health")
async def health() -> Dict[str, str]:
    return {"status": "healthy"}


@app.get("/status")
async def status() -> Dict[str, Any]:
    return await counter_status()


@app.post("/upload/{folder}/{filename}")
async def upload_token_file(
    folder: str,
    filename: str,
    file: UploadFile = File(...),
    server: str = Form(...),
    purpose: str = Form(...),
    file_index: Optional[int] = Form(default=None),
    file_total: Optional[int] = Form(default=None),
    jwt_file_name: Optional[str] = Form(default=None, alias="file_name"),
    authorization: Optional[str] = Header(default=None),
) -> Dict[str, Any]:
    folder = folder.lower().strip()
    filename = filename.strip()

    if UPLOAD_TOKEN and authorization != f"Bearer {UPLOAD_TOKEN}":
        raise HTTPException(status_code=401, detail="unauthorized")

    if folder not in ALLOWED_FOLDERS:
        raise HTTPException(status_code=400, detail="invalid folder")

    normalized_server = normalize_server(server)
    if normalized_server is None:
        raise HTTPException(status_code=400, detail="invalid server")

    expected_filename = f"token_{normalized_server}.json"
    if filename != expected_filename:
        raise HTTPException(status_code=400, detail="invalid filename")

    expected_folder = PURPOSE_TO_FOLDER.get(purpose)
    if expected_folder != folder:
        raise HTTPException(status_code=400, detail="folder purpose mismatch")

    content = await file.read()
    if not content:
        raise HTTPException(status_code=400, detail="empty file")

    try:
        tokens = json.loads(content.decode("utf-8"))
    except Exception:
        raise HTTPException(status_code=400, detail="invalid json")

    is_valid, error = validate_token_payload(tokens)
    if not is_valid:
        raise HTTPException(status_code=400, detail=error)

    save_dir = (JWT_UPLOAD_DIR / folder).resolve()
    save_path = (save_dir / expected_filename).resolve()
    try:
        save_path.relative_to(JWT_UPLOAD_DIR)
    except ValueError:
        raise HTTPException(status_code=400, detail="bad path")

    try:
        atomic_write_json(save_path, tokens)
        write_stored_token_meta(
            save_path,
            {
                "server": normalized_server,
                "purpose": purpose,
                "file_index": file_index,
                "file_total": file_total,
                "file_name": jwt_file_name or upload_filename(file.filename) or expected_filename,
            },
        )
    except Exception as exc:
        logger.error("Error saving uploaded token file: %s", exc)
        raise HTTPException(status_code=500, detail="failed to save token file")

    return {
        "status": "success",
        "folder": folder,
        "filename": expected_filename,
        "saved_to": str(save_path),
        "tokens": len(tokens),
        "size": len(content),
    }


@app.get("/like")
async def like(
    uid: str = Query(...),
    server_name: str = Query(...),
    purpose: str = Query("free"),
    count: int = Query(DEFAULT_LIKE_REQUEST_COUNT, ge=1),
) -> Dict[str, Any]:
    if purpose not in PURPOSE_TO_FOLDER:
        raise HTTPException(status_code=400, detail="invalid purpose")
    normalized_server = normalize_server(server_name)
    if normalized_server is None:
        raise HTTPException(status_code=400, detail="invalid server_name")
    return await process_like(uid, normalized_server.upper(), purpose, count)


@app.get("/like100")
async def like100(uid: str = Query(...), server_name: str = Query(...)) -> Dict[str, Any]:
    normalized_server = normalize_server(server_name)
    if normalized_server is None:
        raise HTTPException(status_code=400, detail="invalid server_name")
    return await process_like(uid, normalized_server.upper(), "like100", 100)


@app.get("/like200")
async def like200(uid: str = Query(...), server_name: str = Query(...)) -> Dict[str, Any]:
    normalized_server = normalize_server(server_name)
    if normalized_server is None:
        raise HTTPException(status_code=400, detail="invalid server_name")
    return await process_like(uid, normalized_server.upper(), "like200", 200)


if __name__ == "__main__":
    import uvicorn

    uvicorn.run("app:app", port=int(os.getenv("PORT", "8000")))
