import mimetypes
import socket
from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from loguru import logger

from app.api import (
    accounts,
    actions,
    auto_accept,
    auto_confirm,
    browser,
    logpass,
    logs,
    mafile_tools,
    proxies,
    tasks,
    token_accounts,
)
from app.api import settings as settings_api
from app.config import settings
from app.core.auto_accept import auto_accept_manager
from app.core.auto_confirm import auto_confirm_manager
from app.core.logging import setup_logging
from app.core.proxy_manager import proxy_manager
from app.database import close_db, get_db
from app.services.registry import register_all_handlers
from version import VERSION

mimetypes.add_type("application/javascript", ".js")
mimetypes.add_type("text/css", ".css")
mimetypes.add_type("image/svg+xml", ".svg")


@asynccontextmanager
async def lifespan(app: FastAPI):
    setup_logging()
    from app.api.logs import install_log_sink

    install_log_sink()
    db = await get_db()

    # Clean up stale tasks left from a previous unclean shutdown
    stale = await db.execute(
        "UPDATE tasks SET status = 'cancelled', error = 'Server restarted', updated_at = datetime('now') "
        "WHERE status = 'running'"
    )
    if stale.rowcount:
        logger.info(f"Startup cleanup: cancelled {stale.rowcount} stale task(s)")
    # Clear per-account result icons so they don't persist after restart
    await db.execute(
        "UPDATE tasks SET account_results = NULL WHERE account_results IS NOT NULL"
    )
    await db.commit()

    await proxy_manager.load()
    register_all_handlers()

    # Resume auto-accept for accounts that had it enabled
    cursor = await db.execute("SELECT * FROM accounts WHERE auto_accept = 1")
    rows = await cursor.fetchall()
    for row in rows:
        account = dict(row)
        if account.get("shared_secret"):
            await auto_accept_manager.start(account)

    # Resume auto-confirm for accounts that had it enabled
    cursor = await db.execute("SELECT * FROM accounts WHERE auto_confirm = 1")
    rows = await cursor.fetchall()
    for row in rows:
        account = dict(row)
        if account.get("identity_secret"):
            await auto_confirm_manager.start(account)

    yield

    await auto_accept_manager.stop_all()
    await auto_confirm_manager.stop_all()
    await close_db()


app = FastAPI(
    title=settings.app_name,
    version=VERSION,
    docs_url="/docs" if settings.debug else None,
    lifespan=lifespan,
)


@app.get("/api/version")
async def get_version():
    return {"version": VERSION}


app.include_router(accounts.router)
app.include_router(actions.router)
app.include_router(proxies.router)
app.include_router(tasks.router)
app.include_router(mafile_tools.router)
app.include_router(logs.router)
app.include_router(auto_accept.router)
app.include_router(auto_confirm.router)
app.include_router(browser.router)
app.include_router(settings_api.router)
app.include_router(logpass.router)
app.include_router(token_accounts.router)

static_dir = settings.base_dir / "app" / "static"
static_dir.mkdir(parents=True, exist_ok=True)

_MIME = {".js": "application/javascript", ".css": "text/css", ".svg": "image/svg+xml"}


@app.get("/", include_in_schema=False)
async def serve_root():
    return FileResponse(
        str(static_dir / "index.html"),
        headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
    )


@app.get("/assets/{filename:path}", include_in_schema=False)
async def serve_asset(filename: str):
    from fastapi import HTTPException

    file_path = static_dir / "assets" / filename
    if not file_path.exists():
        raise HTTPException(status_code=404)
    ext = "." + filename.rsplit(".", 1)[-1] if "." in filename else ""
    media_type = _MIME.get(ext, "application/octet-stream")
    return FileResponse(
        str(file_path),
        media_type=media_type,
        headers={"Cache-Control": "public, max-age=31536000, immutable"},
    )


app.mount("/", StaticFiles(directory=str(static_dir), html=True), name="static")


def find_free_port(start: int = 8000, limit: int = 200) -> int:
    """Return the first free TCP port starting from `start`."""
    for port in range(start, start + limit):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            try:
                s.bind(("127.0.0.1", port))
                return port
            except OSError:
                continue
    raise RuntimeError(f"No free port in range {start}–{start + limit - 1}")


if __name__ == "__main__":
    import threading
    import time
    import webbrowser

    import uvicorn

    # Install log intercept before uvicorn.run() so the first two startup
    # lines (Started server process / Waiting for application startup) are
    # captured by our formatter instead of printing raw.
    setup_logging()

    port = find_free_port(settings.port)
    url = f"http://{settings.host}:{port}"
    tg = "@lolzdm  ->  https://t.me/lolzdm"

    val_w = max(len(url), len(tg))
    inner = val_w + 8  # " URL | " (7) + value + " " (1)

    banner = (
        "\n"
        "\033[34m"
        "  ╔═╗╔╦╗╔═╗╔═╗╔╦╗╔═╗╔═╗╔╗╔╔═╗╦\n"
        "  ╚═╗ ║ ║╣ ╠═╣║║║╠═╝╠═╣║║║║╣ ║\n"
        "  ╚═╝ ╩ ╚═╝╩ ╩╩ ╩╩  ╩ ╩╝╚╝╚═╝╩═╝"
        f"\033[0m \033[90mv{VERSION}\033[0m\n\n"
        f"  \033[90m┌{'─' * inner}┐\033[0m\n"
        f"  \033[90m│\033[0m\033[34m URL \033[90m│\033[0m {url:<{val_w + 1}}\033[90m│\033[0m\n"
        f"  \033[90m│\033[0m\033[34m  TG \033[90m│\033[0m {tg:<{val_w + 1}}\033[90m│\033[0m\n"
        f"  \033[90m└{'─' * inner}┘\033[0m\n"
    )
    print(banner)

    def _open_browser():
        time.sleep(1.5)
        webbrowser.open(url)

    threading.Thread(target=_open_browser, daemon=True).start()

    uvicorn.run(
        "main:app",
        host=settings.host,
        port=port,
        reload=settings.debug,
        log_config=None,
    )
