from datetime import date, timedelta, datetime

from fastapi.testclient import TestClient
from sqlmodel import Session, select

from app.main import app
from app.db.session import engine
from app.models import Child, CoinTransaction, CoinTransactionType, ScreenTimeDayAllowance, ScreenTimeSession
from tests.conftest import admin_headers


def _child(session: Session, name: str = "Emilia") -> Child:
    child = session.exec(select(Child).where(Child.name == name)).first()
    assert child is not None
    return child


def test_screen_time_defaults_start_stop_and_admin_lock():
    client = TestClient(app)
    today = date(2026, 6, 28)
    with Session(engine) as session:
        child = _child(session)
        child_id = child.id
    assert child_id is not None

    response = client.get(f"/api/screen-time?date={today.isoformat()}")
    assert response.status_code == 200
    item = next(row for row in response.json()["items"] if row["child"]["id"] == child_id)
    assert item["allowance"]["total_minutes"] == 40
    assert item["remaining_seconds"] == 2400

    response = client.post(f"/api/screen-time/{child_id}/start", json={"date": today.isoformat(), "idempotency_key": "run"})
    assert response.status_code == 200
    assert response.json()["item"]["is_running"] is True
    with Session(engine) as session:
        active = session.exec(select(ScreenTimeSession).where(ScreenTimeSession.child_id == child_id, ScreenTimeSession.day == today)).first()
        assert active is not None
        active.started_at = datetime(2026, 6, 28, 10, 0, 0)
        session.add(active)
        session.commit()

    response = client.post(f"/api/screen-time/{child_id}/stop", json={"date": today.isoformat()})
    assert response.status_code == 200
    assert response.json()["session"]["status"] == "stopped"
    assert response.json()["session"]["duration_seconds"] > 0
    assert response.json()["item"]["remaining_seconds"] < 2400

    headers = admin_headers(client)
    response = client.patch(f"/api/admin/screen-time/{child_id}/{today.isoformat()}", headers=headers, json={"locked": True})
    assert response.status_code == 200
    assert response.json()["item"]["allowance"]["locked"] is True
    response = client.post(f"/api/screen-time/{child_id}/start", json={"date": today.isoformat(), "idempotency_key": "locked"})
    assert response.status_code == 409


def test_screen_time_shop_exchange_spends_coins_and_adds_today_minutes():
    client = TestClient(app)
    today = date(2026, 6, 28)
    with Session(engine) as session:
      child = _child(session)
      child_id = child.id
      assert child_id is not None
      session.add(CoinTransaction(child_id=child_id, transaction_type=CoinTransactionType.manual, amount=8, comment="test budget", idempotency_key="test-screen-budget"))
      session.commit()

    response = client.post('/api/screen-time/exchange', json={"child_id": child_id, "date": today.isoformat(), "idempotency_key": "swap-1"})
    assert response.status_code == 200
    assert response.json()["item"]["allowance"]["bonus_minutes"] == 10
    assert response.json()["item"]["allowance"]["total_minutes"] == 50

    same = client.post('/api/screen-time/exchange', json={"child_id": child_id, "date": today.isoformat(), "idempotency_key": "swap-1"})
    assert same.status_code == 200
    assert same.json()["idempotent"] is True
    with Session(engine) as session:
        allowance = session.exec(select(ScreenTimeDayAllowance).where(ScreenTimeDayAllowance.child_id == child_id, ScreenTimeDayAllowance.day == today)).first()
        assert allowance is not None
        assert allowance.bonus_minutes == 10
        txs = [tx for tx in session.exec(select(CoinTransaction).where(CoinTransaction.child_id == child_id)).all() if "Bildschirmzeit" in tx.comment]
        assert len(txs) == 1
        assert txs[0].amount == -4


def test_admin_screen_time_week_range_and_adjustment():
    client = TestClient(app)
    headers = admin_headers(client)
    start = date(2026, 6, 28)
    with Session(engine) as session:
        child = _child(session, "Valerie")
        child_id = child.id
    assert child_id is not None

    response = client.get(f"/api/admin/screen-time?start={start.isoformat()}&days=7", headers=headers)
    assert response.status_code == 200
    assert len(response.json()["days"]) == 7

    target = start + timedelta(days=1)
    response = client.patch(f"/api/admin/screen-time/{child_id}/{target.isoformat()}", headers=headers, json={"base_minutes": 45, "adjustment_minutes": -5, "notes": "Test"})
    assert response.status_code == 200
    item = response.json()["item"]
    assert item["allowance"]["total_minutes"] == 40
    assert item["allowance"]["notes"] == "Test"


def test_screen_time_empty_budget_blocks_start():
    client = TestClient(app)
    today = date(2026, 6, 28)
    headers = admin_headers(client)
    with Session(engine) as session:
        child = _child(session)
        child_id = child.id
    assert child_id is not None
    response = client.patch(f"/api/admin/screen-time/{child_id}/{today.isoformat()}", headers=headers, json={"base_minutes": 0, "bonus_minutes": 0, "adjustment_minutes": 0})
    assert response.status_code == 200
    response = client.post(f"/api/screen-time/{child_id}/start", json={"date": today.isoformat()})
    assert response.status_code == 409
    with Session(engine) as session:
        assert session.exec(select(ScreenTimeSession)).all() == []


def test_screen_time_earn_rule_awards_once_and_report_counts_bonus():
    client = TestClient(app)
    today = date(2026, 6, 28)
    with Session(engine) as session:
        child = _child(session)
        child_id = child.id
    assert child_id is not None

    overview = client.get(f"/api/screen-time?date={today.isoformat()}").json()
    item = next(row for row in overview["items"] if row["child"]["id"] == child_id)
    rule = next(rule for rule in item["earning_rules"] if rule["slug"] == "buch-lesen-20")
    assert rule["required_minutes"] == 20
    assert rule["reward_minutes"] == 10
    assert rule["coin_reward"] == 1

    first = client.post("/api/screen-time/earn", json={"child_id": child_id, "rule_id": rule["id"], "date": today.isoformat(), "idempotency_key": "read-once"})
    second = client.post("/api/screen-time/earn", json={"child_id": child_id, "rule_id": rule["id"], "date": today.isoformat(), "idempotency_key": "read-once"})
    assert first.status_code == 200
    assert first.json()["item"]["allowance"]["bonus_minutes"] == 10
    assert first.json()["item"]["allowance"]["total_minutes"] == 50
    with Session(engine) as session:
        txs = [tx for tx in session.exec(select(CoinTransaction).where(CoinTransaction.child_id == child_id)).all() if "Bildschirmzeit verdienen" in tx.comment]
        assert len(txs) == 1
        assert txs[0].amount == 1
    assert second.status_code == 200
    assert second.json()["idempotent"] is True

    undo = client.post("/api/screen-time/earn/undo", json={"child_id": child_id, "rule_id": rule["id"], "date": today.isoformat()})
    assert undo.status_code == 200
    assert undo.json()["item"]["allowance"]["bonus_minutes"] == 0
    with Session(engine) as session:
        txs = [tx for tx in session.exec(select(CoinTransaction).where(CoinTransaction.child_id == child_id)).all() if "Bildschirmzeit verdienen" in tx.comment]
        assert [tx.amount for tx in txs] == [1, -1]
    redo = client.post("/api/screen-time/earn", json={"child_id": child_id, "rule_id": rule["id"], "date": today.isoformat(), "idempotency_key": "read-again"})
    assert redo.status_code == 200
    assert redo.json()["item"]["allowance"]["bonus_minutes"] == 10

    headers = admin_headers(client)
    report = client.get(f"/api/admin/screen-time/report?start={today.isoformat()}&days=7", headers=headers).json()
    summary = next(row for row in report["summary"] if row["child"]["id"] == child_id)
    assert summary["earned_minutes"] == 10


def test_admin_can_create_and_update_screen_time_earn_rule_with_coin_reward():
    client = TestClient(app)
    headers = admin_headers(client)
    with Session(engine) as session:
        child = _child(session, "Valerie")
        child_id = child.id
    assert child_id is not None

    created = client.post("/api/admin/screen-time/earn-rules", headers=headers, json={
        "slug": "test-klavier-20",
        "title": "20 Minuten Klavier üben",
        "description": "",
        "required_minutes": 20,
        "reward_minutes": 10,
        "coin_reward": 2,
        "child_id": child_id,
        "active": True,
        "sort_order": 99,
    })
    assert created.status_code == 201
    rule = created.json()["rule"]
    assert rule["coin_reward"] == 2
    assert rule["child_id"] == child_id

    patched = client.patch(f"/api/admin/screen-time/earn-rules/{rule['id']}", headers=headers, json={"coin_reward": 3, "child_id": None})
    assert patched.status_code == 200
    assert patched.json()["rule"]["coin_reward"] == 3
    assert patched.json()["rule"]["child_id"] is None
