"""M2 rally mechanics: serve-on-create, the explicit pass, and the status
automations as ball MOVEMENT (auto-spike + rally memory, revision restore,
resolve clears + ace, reopen, Informational -> FYI Only). These are the
volleyball consequences layered on the proven review-app state machine."""
from __future__ import annotations

import uuid

from app import constants
from app.models import Ball, Court, Match, Side, TeamMember, TeamNode, Touch
from tests.conftest import make_player, mint_client_for


def _issues_board(db):
    rian = make_player(db, "rian", display_name="Rian")
    erin = make_player(db, "erin", display_name="Erin")
    adi = make_player(db, "adi", display_name="Adi")

    match = Match(slug="rallymatch", name="Rally Match")
    db.add(match)
    db.flush()
    side = Side(match_id=match.id, name="Bowden Works", position=1)
    db.add(side)
    db.flush()
    node = TeamNode(side_id=side.id, name="Front line", boundary_player_id=rian.id)
    db.add(node)
    db.flush()
    for p in (rian, erin, adi):
        db.add(TeamMember(match_id=match.id, node_id=node.id, player_id=p.id))

    issues = Court(
        match_id=match.id,
        config_key="issues",
        name="Issues",
        position=0,
        config=constants.COURT_CONFIGS["issues"],
    )
    db.add(issues)
    db.commit()
    return {"match": match, "court": issues, "rian": rian, "erin": erin, "adi": adi}


def _touches(db, ball_id):
    return (
        db.query(Touch).filter(Touch.ball_id == ball_id).order_by(Touch.seq).all()
    )


def test_create_records_serve_touch(client, db_session):
    b = _issues_board(db_session)
    c = mint_client_for(client, "erin")
    resp = c.post(
        "/api/matches/rallymatch/balls",
        json={
            "court_id": str(b["court"].id),
            "title": "Footer is broken",
            "category": "Bug",
            "action_to": [str(b["rian"].id)],
        },
    )
    assert resp.status_code == 201, resp.text
    touches = _touches(db_session, uuid.UUID(resp.json()["id"]))
    assert [t.kind for t in touches] == ["serve"]
    assert touches[0].from_player == b["erin"].id
    assert touches[0].to_players == [b["rian"].id]


def test_pass_endpoint_moves_ball_and_records_touch(client, db_session):
    b = _issues_board(db_session)
    ball = Ball(court_id=b["court"].id, title="Tweak nav", status="Open",
                category="Revision", action_to=[b["rian"].id], author_id=b["erin"].id)
    db_session.add(ball)
    db_session.commit()

    c = mint_client_for(client, "rian")
    resp = c.post(f"/api/balls/{ball.id}/pass", json={"to_players": [str(b["adi"].id)]})
    assert resp.status_code == 200, resp.text
    assert resp.json()["action_to"] == [str(b["adi"].id)]
    kinds = [t.kind for t in _touches(db_session, ball.id)]
    assert kinds == ["pass"]
    # a plain pass never disturbs rally memory
    db_session.refresh(ball)
    assert ball.prev_action_to == []


def test_ready_for_review_auto_spikes_to_author_with_rally_memory(client, db_session):
    b = _issues_board(db_session)
    ball = Ball(court_id=b["court"].id, title="Hero copy", status="Addressing",
                category="Revision", action_to=[b["adi"].id], author_id=b["erin"].id)
    db_session.add(ball)
    db_session.commit()

    c = mint_client_for(client, "adi")
    resp = c.patch(f"/api/balls/{ball.id}", json={"status": "Ready for Review"})
    assert resp.status_code == 200, resp.text
    body = resp.json()
    assert body["action_to"] == [str(b["erin"].id)]          # spiked to author
    assert body["prev_action_to"] == [str(b["adi"].id)]      # rally memory
    assert [t.kind for t in _touches(db_session, ball.id)] == ["spike"]


def test_revision_required_restores_rally_memory(client, db_session):
    b = _issues_board(db_session)
    ball = Ball(court_id=b["court"].id, title="Hero copy", status="Ready for Review",
                category="Revision", action_to=[b["erin"].id],
                prev_action_to=[b["adi"].id], author_id=b["erin"].id)
    db_session.add(ball)
    db_session.commit()

    c = mint_client_for(client, "erin")
    resp = c.patch(f"/api/balls/{ball.id}", json={"status": "Revision Required"})
    assert resp.status_code == 200, resp.text
    assert resp.json()["action_to"] == [str(b["adi"].id)]    # returned to setter
    assert [t.kind for t in _touches(db_session, ball.id)] == ["pass"]


def test_resolve_clears_hands_and_stamps(client, db_session):
    b = _issues_board(db_session)
    ball = Ball(court_id=b["court"].id, title="Hero copy", status="Ready for Review",
                category="Revision", action_to=[b["erin"].id], author_id=b["erin"].id)
    db_session.add(ball)
    db_session.commit()
    # a prior rally exists (serve + spike), so this is NOT an ace
    from app.services import touches as touch_service
    touch_service.record_touch(db_session, ball, "serve", b["erin"], [b["adi"].id])
    touch_service.record_touch(db_session, ball, "spike", b["adi"], [b["erin"].id])
    db_session.commit()

    c = mint_client_for(client, "erin")
    resp = c.patch(f"/api/balls/{ball.id}", json={"status": "Resolved"})
    assert resp.status_code == 200, resp.text
    body = resp.json()
    assert body["action_to"] == []
    assert body["resolved_at"] is not None
    assert body["ace"] is False
    assert [t.kind for t in _touches(db_session, ball.id)][-1] == "resolve"


def test_one_touch_resolve_is_an_ace(client, db_session):
    b = _issues_board(db_session)
    c = mint_client_for(client, "erin")
    created = c.post(
        "/api/matches/rallymatch/balls",
        json={"court_id": str(b["court"].id), "title": "Typo on About",
              "category": "Revision", "action_to": [str(b["rian"].id)]},
    ).json()

    c2 = mint_client_for(client, "rian")
    resp = c2.patch(f"/api/balls/{created['id']}", json={"status": "Resolved"})
    assert resp.status_code == 200, resp.text
    assert resp.json()["ace"] is True                        # serve -> put away


def test_reopen_restores_hands(client, db_session):
    b = _issues_board(db_session)
    ball = Ball(court_id=b["court"].id, title="Hero copy", status="Resolved",
                category="Revision", action_to=[], prev_action_to=[b["adi"].id],
                author_id=b["erin"].id)
    db_session.add(ball)
    db_session.commit()

    c = mint_client_for(client, "erin")
    resp = c.patch(f"/api/balls/{ball.id}", json={"status": "Open"})
    assert resp.status_code == 200, resp.text
    body = resp.json()
    assert body["action_to"] == [str(b["adi"].id)]
    assert body["resolved_at"] is None
    assert [t.kind for t in _touches(db_session, ball.id)] == ["reopen"]


def test_informational_category_becomes_fyi_announcement(client, db_session):
    b = _issues_board(db_session)
    ball = Ball(court_id=b["court"].id, title="Launch is Tuesday", status="Open",
                category="Revision", author_id=b["erin"].id)
    db_session.add(ball)
    db_session.commit()

    c = mint_client_for(client, "erin")
    resp = c.patch(f"/api/balls/{ball.id}", json={"category": "Informational"})
    assert resp.status_code == 200, resp.text
    assert resp.json()["status"] == "FYI Only"

    # and at creation time too
    created = c.post(
        "/api/matches/rallymatch/balls",
        json={"court_id": str(b["court"].id), "title": "New brand font",
              "category": "Informational"},
    ).json()
    assert created["status"] == "FYI Only"


def test_rally_endpoint_lists_touches_in_order(client, db_session):
    b = _issues_board(db_session)
    c = mint_client_for(client, "erin")
    created = c.post(
        "/api/matches/rallymatch/balls",
        json={"court_id": str(b["court"].id), "title": "Chain test",
              "category": "Revision", "action_to": [str(b["rian"].id)]},
    ).json()
    c2 = mint_client_for(client, "rian")
    c2.post(f"/api/balls/{created['id']}/pass", json={"to_players": [str(b["adi"].id)]})
    c3 = mint_client_for(client, "adi")
    c3.patch(f"/api/balls/{created['id']}", json={"status": "Ready for Review"})

    rally = c.get(f"/api/balls/{created['id']}/rally").json()
    assert [t["kind"] for t in rally["touches"]] == ["serve", "pass", "spike"]
    assert rally["touches"][0]["seq"] == 1
