"""
Messier Object Guessing Game — Flask API
Размещать: src/web/messier_api.py

Запуск:
    cd <project_root>
    pip install flask
    python src/web/messier_api.py

API:
    POST /api/messier/start          — начать новую игру
    GET  /api/messier/image          — получить изображение текущего объекта
    POST /api/messier/answer         — отправить ответ
    GET  /api/messier/score          — получить итог
"""

import io
import random
import uuid
from threading import Lock
from typing import Any, Dict

import matplotlib
import matplotlib.pyplot as plt
from flask import Flask, jsonify, request, send_file

from src.hip_catalog.hip_catalog import Catalog, CatalogConstraints
from src.messier.messier_catalog import MessierCatalog, MessierType
from src.pinhole_projection.pinhole_projector import (
    CameraConfig,
    ConstellationConfig,
    Pinhole,
    PinholeConfig,
    ShotConditions,
)
from src.planets_catalog.planet_catalog import PlanetCatalog

matplotlib.use("Agg")

app = Flask(__name__)

# ── Хранилище сессий (in-memory, подходит для небольшой нагрузки) ────────────
sessions: Dict[str, Any] = {}
sessions_lock = Lock()

# ── Разделяемые каталоги (инициализируются один раз) ─────────────────────────
star_catalog = Catalog(catalog_name="hip_data.tsv", use_cache=True)
planet_catalog = PlanetCatalog()
messier_catalog = MessierCatalog()


def _make_session(num_rounds: int) -> dict:
    """Создать новую игровую сессию."""
    all_objects = messier_catalog.get_all_objects()
    chosen = random.sample(
        list(all_objects), min(num_rounds, len(all_objects))
    )
    return {
        "rounds": chosen,  # список объектов
        "current": 0,  # текущий раунд
        "score": 0,
        "answered": False,  # ожидаем ли мы ответ прямо сейчас
        "results": [],  # история ответов
    }


def _render_pinhole_png(messier_object) -> bytes:
    """Рендерит pinhole-проекцию объекта в PNG-байты."""
    camera_config = CameraConfig.from_fov_and_aspect(
        fov_deg=60,
        aspect_ratio=1.5,
        height_pix=600,
    )

    ra = float(messier_object["ra"])
    dec = float(messier_object["dec"])

    import numpy as np

    direction = np.array(
        [
            np.cos(dec) * np.cos(ra),
            np.cos(dec) * np.sin(ra),
            np.sin(dec),
        ]
    )

    shot_cond = ShotConditions(
        center_direction=direction,
        tilt_angle=0.0,
    )

    constellation_config = ConstellationConfig(
        constellation_linewidth=0.5,
        constellation_alpha=0.5,
    )

    config = PinholeConfig()
    projector = Pinhole(
        shot_cond=shot_cond,
        camera_cfg=camera_config,
        config=config,
        constellation_config=constellation_config,
        catalog=star_catalog,
        planet_catalog=planet_catalog,
    )

    constraints = CatalogConstraints(max_magnitude=6.5)
    fig, ax = projector.generate(constraints=constraints)
    ax.set_aspect("equal")

    # Маркер объекта
    cx = camera_config.width / 2
    cy = camera_config.height / 2
    marker_size = max(20, min(200, float(messier_object["size"]) * 2))
    color = MessierCatalog.get_type_color(
        MessierType(messier_object["obj_type"])
    )
    ax.scatter(
        cx,
        cy,
        s=marker_size,
        marker="o",
        facecolors="none",
        edgecolors=color,
        linewidths=2,
        alpha=0.8,
    )
    ax.scatter(cx, cy, s=10, marker=".", c=color, alpha=0.9)

    # Подпись (без номера объекта — это загадка)
    obj_type = MessierCatalog.get_type_name(
        MessierType(messier_object["obj_type"])
    )
    ax.set_title(
        f"Тип: {obj_type}  |  Зв. вел.: {messier_object['v_mag']:.1f}  "
        f"|  Угл. размер: {messier_object['size']:.1f}'  "
        f"|  Созвездие: {messier_object['constellation']}",
        fontsize=10,
        pad=14,
        color="white",
    )
    fig.patch.set_facecolor("#0a0e1a")

    buf = io.BytesIO()
    fig.savefig(
        buf,
        format="png",
        dpi=100,
        bbox_inches="tight",
        facecolor=fig.get_facecolor(),
    )
    plt.close(fig)
    buf.seek(0)
    return buf.read()


@app.after_request
def add_cors(response):
    response.headers["Access-Control-Allow-Origin"] = "*"
    response.headers["Access-Control-Allow-Headers"] = "Content-Type"
    response.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"
    return response


@app.route("/api/messier/<path:_>", methods=["OPTIONS"])
def options_handler(_):
    return jsonify({}), 200


@app.route("/api/messier/start", methods=["POST"])
def start_game():
    """
    Body (JSON): { "num_rounds": 5 }
    Response:    { "session_id": "...", "num_rounds": 5 }
    """
    data = request.get_json(silent=True) or {}
    num_rounds = max(1, min(110, int(data.get("num_rounds", 5))))

    session_id = str(uuid.uuid4())
    session = _make_session(num_rounds)

    with sessions_lock:
        sessions[session_id] = session

    return jsonify(
        {
            "session_id": session_id,
            "num_rounds": len(session["rounds"]),
        }
    )


@app.route("/api/messier/image", methods=["GET"])
def get_image():
    """
    Query: ?session_id=...
    Returns PNG image of the current Messier object.
    """
    session_id = request.args.get("session_id")
    with sessions_lock:
        session = sessions.get(session_id)

    if not session:
        return jsonify({"error": "Сессия не найдена"}), 404

    idx = session["current"]
    if idx >= len(session["rounds"]):
        return jsonify({"error": "Игра уже завершена"}), 400

    obj = session["rounds"][idx]
    png = _render_pinhole_png(obj)
    return send_file(io.BytesIO(png), mimetype="image/png")


@app.route("/api/messier/answer", methods=["POST"])
def submit_answer():
    """
    Body: { "session_id": "...", "guess": 42 }
    Response: {
        "correct": bool,
        "correct_number": int,
        "name": str,
        "type": str,
        "constellation": str,
        "score": int,
        "round": int,
        "total_rounds": int,
        "game_over": bool
    }
    """
    data = request.get_json(silent=True) or {}
    session_id = data.get("session_id")
    guess = data.get("guess")

    with sessions_lock:
        session = sessions.get(session_id)

    if not session:
        return jsonify({"error": "Сессия не найдена"}), 404

    try:
        guess = int(guess)
    except (TypeError, ValueError):
        return jsonify({"error": "Некорректное число"}), 400

    idx = session["current"]
    if idx >= len(session["rounds"]):
        return jsonify({"error": "Игра уже завершена"}), 400

    obj = session["rounds"][idx]
    correct = int(obj["m_number"])
    is_correct = guess == correct

    if is_correct:
        session["score"] += 1

    obj_type = MessierCatalog.get_type_name(MessierType(obj["obj_type"]))
    name = str(obj.get("name", "")) if obj.get("name") else None

    session["results"].append(
        {
            "round": idx + 1,
            "guess": guess,
            "correct": correct,
            "is_correct": is_correct,
        }
    )
    session["current"] += 1
    game_over = session["current"] >= len(session["rounds"])

    return jsonify(
        {
            "correct": is_correct,
            "correct_number": correct,
            "name": name,
            "type": obj_type,
            "constellation": str(obj["constellation"]),
            "magnitude": float(obj["v_mag"]),
            "size": float(obj["size"]),
            "score": session["score"],
            "round": idx + 1,
            "total_rounds": len(session["rounds"]),
            "game_over": game_over,
        }
    )


@app.route("/api/messier/score", methods=["GET"])
def get_score():
    """
    Query: ?session_id=...
    Response: { "score": int, "total": int, "results": [...] }
    """
    session_id = request.args.get("session_id")
    with sessions_lock:
        session = sessions.get(session_id)

    if not session:
        return jsonify({"error": "Сессия не найдена"}), 404

    return jsonify(
        {
            "score": session["score"],
            "total": len(session["rounds"]),
            "results": session["results"],
        }
    )


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000, debug=False)
