from __future__ import annotations

import os
from pathlib import Path
from typing import Any

import httpx
from dotenv import load_dotenv
from fastapi import Depends, FastAPI, HTTPException, Query, Request
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles

from hockey_data import (
    HockeyDataService,
    HockeyDatabase,
    HockeySettingsStore,
    create_hockey_router,
)
from hockey_data.router import create_hockey_vmix_router
from hockey_data.vmix_portable import (
    HockeyVmixPortable,
    configure_hockey_vmix_settings,
    create_hockey_vmix_settings_router,
)
from hockey_data.auth_bridge import HockeyAuthDependencies, create_auth_adapter
from hockey_data.auth_router import create_hockey_auth_router
from hockey_data.agent_bridge import VmixAgentHub, create_hockey_agent_router
from ui_builder import install_ui_builder
from khl_site.khl_data_center import APP as khl_site_app

BASE_DIR = Path(__file__).resolve().parent
BUILD_VERSION = "2026.08.27.2"
# compatibility: BUILD_VERSION = "2026.08.26.1"
# compatibility: BUILD_VERSION = "2026.08.25.1"
# compatibility: BUILD_VERSION = "2026.08.24.15"
# compatibility: BUILD_VERSION = "2026.08.24.14"
# compatibility: BUILD_VERSION = "2026.08.24.13"
# compatibility: BUILD_VERSION = "2026.08.24.8"
# compatibility: BUILD_VERSION = "2026.08.24.7"
# compatibility: BUILD_VERSION = "2026.08.24.6"
# compatibility: BUILD_VERSION = "2026.08.24.5"
# compatibility: BUILD_VERSION = "2026.08.24.4"
# compatibility: BUILD_VERSION = "2026.08.24.2"
# compatibility: BUILD_VERSION = "2026.08.24.1"
# compatibility: BUILD_VERSION = "2026.08.21.1"
# compatibility: BUILD_VERSION = "2026.08.20.17"
# compatibility: BUILD_VERSION = "2026.08.20.16"
# compatibility: BUILD_VERSION = "2026.08.20.15"
# compatibility: BUILD_VERSION = "2026.08.20.14"
# compatibility: BUILD_VERSION = "2026.08.20.13"
# compatibility: BUILD_VERSION = "2026.08.19.28"
# compatibility: BUILD_VERSION = "2026.08.19.24"
# compatibility: BUILD_VERSION = "2026.08.19.22"
load_dotenv(BASE_DIR / ".env.local", override=False)
load_dotenv(BASE_DIR / ".env", override=False)

# Production can keep secrets outside the Git working tree. systemd already
# injects EnvironmentFile=/mnt/khl/.env, but this fallback also makes a manual
# `python -m uvicorn app:app` launch read the same file. Existing process
# environment variables always win because override=False.
_external_env = Path(os.getenv("HOCKEY_ENV_FILE", "/mnt/khl/.env"))
if _external_env.is_file():
    load_dotenv(_external_env, override=False)
VMIX_API_URL = os.getenv("VMIX_API_URL", "http://127.0.0.1:8088/api/")
configure_hockey_vmix_settings(BASE_DIR)

app = FastAPI(title=f"Хоккейная панель управления / Stat2TV — build {BUILD_VERSION}")

print(f"[Hockey Build] {BUILD_VERSION}")
hockey_settings = HockeySettingsStore(BASE_DIR / "settings")
hockey_database = HockeyDatabase(BASE_DIR)
hockey_auth = HockeyAuthDependencies(create_auth_adapter(hockey_database))
hockey_agent_hub = VmixAgentHub(hockey_database, settings=hockey_settings)


@app.on_event("startup")
async def hockey_mapping_auto_refresh_startup() -> None:
    hockey_agent_hub.start_auto_refresh()


@app.on_event("shutdown")
async def hockey_mapping_auto_refresh_shutdown() -> None:
    await hockey_agent_hub.stop_auto_refresh()


@app.middleware("http")
async def hockey_runtime_auth(request: Request, call_next):
    """Protect runtime/editor and prevent stale operator-interface assets."""
    path = request.url.path
    editor_path = (
        path == "/editor"
        or path.startswith("/api/ui-builder/editor/")
        or path.startswith("/api/ui-builder/auth/")
    )
    khl_admin_path = path.startswith("/khl-site")
    protected_path = path == "/" or khl_admin_path or editor_path

    if protected_path:
        try:
            user = await hockey_auth.optional_user(request)
        except HTTPException as error:
            return JSONResponse({"detail": error.detail}, status_code=error.status_code)
        if user is None:
            if path in {"/", "/editor"}:
                return RedirectResponse("/login?reason=expired", status_code=303)
            return JSONResponse({"detail": "Требуется вход"}, status_code=401)
        if (khl_admin_path or editor_path) and not user.is_admin:
            if path == "/editor":
                return RedirectResponse("/", status_code=303)
            return JSONResponse({"detail": "Раздел доступен только администратору"}, status_code=403)
        request.state.wfl_user = user
    response = await call_next(request)
    if (
        path in {"/", "/editor"}
        or path.startswith("/ui-builder-assets/")
        or path.startswith("/hockey-assets/")
        or path.startswith("/api/ui-builder/runtime/")
        or path.startswith("/khl-site")
    ):
        response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
        response.headers["Pragma"] = "no-cache"
        response.headers["Expires"] = "0"
    return response
hockey_service = HockeyDataService(
    base_dir=BASE_DIR,
    database=hockey_database,
    settings=hockey_settings,
)
hockey_vmix_portable = HockeyVmixPortable(hockey_service)
HOCKEY_BOOTSTRAP = hockey_service.bootstrap()
_backfill = HOCKEY_BOOTSTRAP.get("normalized_backfill", {}) if isinstance(HOCKEY_BOOTSTRAP, dict) else {}
if any(int(value or 0) for value in _backfill.values() if isinstance(value, (int, float))):
    print(
        "[Hockey DB] normalized backfill updated "
        + " ".join(f"{key}={value}" for key, value in _backfill.items())
    )
HOCKEY_SCHEMA_STATUS = hockey_database.schema_status()
print(
    "[Hockey DB] schema ok="
    f"{HOCKEY_SCHEMA_STATUS.get('ok')} "
    f"repaired={len(HOCKEY_SCHEMA_STATUS.get('repaired', []))} "
    f"rebuilt={HOCKEY_SCHEMA_STATUS.get('rebuilt', False)}"
)

app.mount("/khl-site", khl_site_app)

app.mount(
    "/hockey-assets",
    StaticFiles(directory=BASE_DIR / "hockey_data" / "static"),
    name="hockey-assets",
)


def hockey_stat2tv_data() -> dict[str, Any]:
    return hockey_service.data_for_builder()


def vmix_demo_data() -> dict[str, Any]:
    home_players = [
        {"id": f"H{i:02d}", "number": number, "name": name, "position": position}
        for i, (number, name, position) in enumerate([
            (1, "Максим Лебедев", "ВР"), (30, "Илья Зорин", "ВР"),
            (2, "Алексей Титов", "ЗАЩ"), (4, "Егор Савельев", "ЗАЩ"),
            (7, "Антон Крылов", "НАП"), (9, "Даниил Орехов", "НАП"),
            (10, "Никита Сергеев", "НАП"), (11, "Михаил Сафонов", "НАП"),
            (13, "Владимир Комаров", "ЗАЩ"), (15, "Роман Беляев", "НАП"),
            (17, "Андрей Иванов", "НАП"), (19, "Степан Волков", "НАП"),
            (21, "Кирилл Панов", "ЗАЩ"), (23, "Олег Миронов", "НАП"),
            (25, "Арсений Белов", "НАП"), (27, "Матвей Фролов", "ЗАЩ"),
            (29, "Глеб Мельников", "НАП"), (44, "Павел Кузнецов", "ЗАЩ"),
            (71, "Иван Ларионов", "НАП"), (77, "Семён Жуков", "НАП"),
            (81, "Фёдор Егоров", "НАП"), (91, "Артём Петров", "НАП"),
        ], start=1)
    ]
    away_players = [
        {"id": f"A{i:02d}", "number": number, "name": name, "position": position}
        for i, (number, name, position) in enumerate([
            (20, "Лев Корнеев", "ВР"), (35, "Тимур Котов", "ВР"),
            (3, "Ярослав Сорокин", "ЗАЩ"), (5, "Денис Макаров", "ЗАЩ"),
            (8, "Георгий Гусев", "НАП"), (12, "Виктор Назаров", "НАП"),
            (14, "Руслан Власов", "НАП"), (16, "Борис Тарасов", "ЗАЩ"),
            (18, "Марк Селезнёв", "НАП"), (22, "Станислав Никифоров", "ЗАЩ"),
            (24, "Игорь Соколов", "НАП"), (28, "Вадим Фомин", "НАП"),
            (32, "Пётр Громов", "ЗАЩ"), (37, "Леонид Васильев", "НАП"),
            (41, "Константин Ершов", "ЗАЩ"), (55, "Захар Захаров", "ЗАЩ"),
            (61, "Александр Осипов", "НАП"), (68, "Сергей Орлов", "НАП"),
            (73, "Дмитрий Богданов", "НАП"), (84, "Валерий Маслов", "НАП"),
            (88, "Николай Голубев", "НАП"), (97, "Анатолий Чернов", "НАП"),
        ], start=1)
    ]
    return {
        "vmix": {
            "api_url": VMIX_API_URL,
            "function": "OverlayInput4In",
            "input": 2,
        },
        "hockey": {
            "home": {"name": "СЕВЕР", "players": home_players},
            "away": {"name": "ВОСТОК", "players": away_players},
        },
    }


@app.get("/api/vmix/command")
async def vmix_command(
    function: str = Query(..., min_length=1),
    input_value: str | None = Query(default=None),
) -> dict[str, Any]:
    """Send a shortcut command to the local vMix HTTP API.

    The browser calls this same-origin FastAPI route. FastAPI then calls vMix,
    so browser CORS restrictions do not block the command.
    """
    params: dict[str, str] = {"Function": function}
    if input_value not in (None, ""):
        params["Input"] = input_value

    try:
        async with httpx.AsyncClient(timeout=5.0) as client:
            response = await client.get(VMIX_API_URL, params=params)
    except httpx.RequestError as error:
        raise HTTPException(
            status_code=502,
            detail=f"Не удалось подключиться к vMix по адресу {VMIX_API_URL}: {error}",
        ) from error

    if response.status_code != 200:
        raise HTTPException(
            status_code=502,
            detail={
                "message": "vMix вернул ошибку",
                "status": response.status_code,
                "response": response.text[:1000],
            },
        )

    return {
        "ok": True,
        "vmix_url": str(response.request.url),
        "function": function,
        "input": input_value,
    }


install_ui_builder(
    app,
    build_version=BUILD_VERSION,
    settings_dir=BASE_DIR / "settings",
    data_providers={
        "hockey_stat2tv": hockey_stat2tv_data,
        "vmix_demo": vmix_demo_data,
    },
    source_labels={
        "hockey_stat2tv": "Stat2TV — хоккей",
        "vmix_demo": "vMix — тестовые данные",
    },
    editor_url="/editor",
    runtime_url="/",
    custom_script_urls=[
        f"/hockey-assets/tournament-menu.js?v={BUILD_VERSION}",
        f"/hockey-assets/admin-directories.js?v={BUILD_VERSION}",
        "/hockey-assets/match-status-header.js",
        "/hockey-assets/agent-devices.js",
    ],
    custom_style_urls=[
        "/hockey-assets/tournament-menu.css",
        f"/hockey-assets/admin-directories.css?v={BUILD_VERSION}",
        "/hockey-assets/match-status-header.css",
        "/hockey-assets/agent-devices.css",
    ],
)


@app.post(
    "/api/hockey/ui/prematch-buttons",
    dependencies=[Depends(hockey_auth.require_user)],
    tags=["Hockey UI"],
)
async def save_hockey_prematch_buttons(payload: dict[str, Any]) -> dict[str, Any]:
    """Save operator prematch buttons without replacing the rest of UI Builder config."""
    buttons = payload.get("prematch_buttons")
    groups = payload.get("prematch_groups", [])
    selectors = payload.get("quick_panel_selectors", [])
    player_panels = payload.get("player_selection_panels", [])
    if not isinstance(buttons, list):
        raise HTTPException(status_code=422, detail="prematch_buttons must be a list")
    if not isinstance(groups, list):
        raise HTTPException(status_code=422, detail="prematch_groups must be a list")
    if not isinstance(selectors, list):
        raise HTTPException(status_code=422, detail="quick_panel_selectors must be a list")
    if not isinstance(player_panels, list):
        raise HTTPException(status_code=422, detail="player_selection_panels must be a list")

    draft_manager = app.state.ui_builder_draft_manager
    published_manager = app.state.ui_builder_published_manager

    draft_config = draft_manager.load()
    draft_config["prematch_groups"] = groups
    draft_config["prematch_buttons"] = buttons
    draft_config["quick_panel_selectors"] = selectors
    draft_config["player_selection_panels"] = player_panels
    saved_draft = draft_manager.save(draft_config)

    published_config = published_manager.load()
    published_config["prematch_groups"] = saved_draft.get("prematch_groups", [])
    published_config["prematch_buttons"] = saved_draft.get("prematch_buttons", [])
    published_config["quick_panel_selectors"] = saved_draft.get("quick_panel_selectors", [])
    published_config["player_selection_panels"] = saved_draft.get("player_selection_panels", [])
    saved_published = published_manager.save(published_config, create_backup=True)

    return {
        "ok": True,
        "prematch_groups": saved_published.get("prematch_groups", []),
        "prematch_buttons": saved_published.get("prematch_buttons", []),
        "quick_panel_selectors": saved_published.get("quick_panel_selectors", []),
        "player_selection_panels": saved_published.get("player_selection_panels", []),
        "config": saved_published,
    }


@app.post(
    "/api/hockey/ui/triggers",
    dependencies=[Depends(hockey_auth.require_user)],
    tags=["Hockey UI"],
)
async def save_hockey_ui_triggers(payload: dict[str, Any]) -> dict[str, Any]:
    """Save only UI triggers for an authenticated operator, preserving all other config."""
    triggers = payload.get("triggers")
    if not isinstance(triggers, list):
        raise HTTPException(status_code=422, detail="triggers must be a list")

    draft_manager = app.state.ui_builder_draft_manager
    published_manager = app.state.ui_builder_published_manager

    draft_config = draft_manager.load()
    draft_config["triggers"] = triggers
    saved_draft = draft_manager.save(draft_config)

    published_config = published_manager.load()
    published_config["triggers"] = saved_draft.get("triggers", [])
    saved_published = published_manager.save(published_config, create_backup=True)

    return {
        "ok": True,
        "triggers": saved_published.get("triggers", []),
    }


async def _require_ui_builder_editor(request: Request) -> None:
    await app.state.ui_builder_auth.require_editor(request)


@app.get(
    "/api/ui-builder/editor/vmix-inventory",
    dependencies=[Depends(_require_ui_builder_editor)],
    tags=["UI Builder"],
)
async def ui_builder_vmix_inventory() -> dict[str, Any]:
    return await hockey_agent_hub.latest_vmix_inventory()


app.include_router(
    create_hockey_router(
        hockey_service,
        auth_dependency=hockey_auth.require_user,
        admin_dependency=hockey_auth.require_admin,
        agent_hub=hockey_agent_hub,
    )
)
app.include_router(create_hockey_auth_router(hockey_auth))
app.include_router(
    create_hockey_agent_router(
        hockey_agent_hub,
        auth_dependency=hockey_auth.require_user,
        admin_dependency=hockey_auth.require_admin,
    )
)
app.include_router(
    create_hockey_vmix_settings_router(
        hockey_vmix_portable,
        admin_dependency=hockey_auth.require_admin,
    )
)
app.include_router(create_hockey_vmix_router(hockey_service, hockey_vmix_portable))

app.state.hockey_service = hockey_service
app.state.hockey_database = hockey_database
app.state.hockey_settings = hockey_settings
app.state.hockey_auth = hockey_auth
app.state.hockey_vmix_portable = hockey_vmix_portable
app.state.hockey_agent_hub = hockey_agent_hub


@app.get("/runtime", include_in_schema=False)
async def legacy_runtime_redirect() -> RedirectResponse:
    return RedirectResponse("/", status_code=302)


@app.get("/ui-builder", include_in_schema=False)
async def legacy_editor_redirect() -> RedirectResponse:
    # Legacy/bookmarked UI Builder URLs should always land in the operator runtime.
    # The editor is opened explicitly from the runtime toolbar after PIN auth.
    return RedirectResponse("/", status_code=302)
