# SPDX-License-Identifier: AGPL-3.0-only
# Copyright (c) 2026 sol pbc

from __future__ import annotations

import re
from datetime import date as real_date
from datetime import timedelta
from pathlib import Path

import pytest

from solstone.apps.tokens import routes as token_routes


def _day(offset: int, today: real_date | None = None) -> str:
    base = today or real_date.today()
    return (base - timedelta(days=offset)).strftime("%Y%m%d")


def _entry(model: str, total_tokens: int) -> dict:
    return {
        "timestamp": 1772676000.0,
        "model": model,
        "context": "think.cortex.flow:42",
        "usage": {
            "input_tokens": total_tokens // 2,
            "output_tokens": total_tokens // 2,
            "total_tokens": total_tokens,
        },
    }


def _patch_token_cost(monkeypatch):
    def calc_cost(entry: dict) -> dict:
        total_tokens = entry.get("usage", {}).get("total_tokens", 0) or 0
        return {
            "total_cost": total_tokens / 10000,
            "input_cost": 0.0,
            "output_cost": total_tokens / 10000,
            "currency": "USD",
        }

    monkeypatch.setattr(token_routes, "calc_token_cost", calc_cost)


def _tokens_snapshot(journal: Path) -> dict[Path, int]:
    tokens = journal / "tokens"
    if not tokens.exists():
        return {}
    return {path: path.stat().st_mtime_ns for path in sorted(tokens.rglob("*"))}


def _static_empty_cell(html: str, tbody_id: str) -> str:
    match = re.search(
        rf'<tbody id="{re.escape(tbody_id)}">\s*'
        r'<tr class="empty-row">\s*'
        r'<td colspan="\d+">([^<]+)</td>\s*'
        r"</tr>\s*</tbody>",
        html,
        re.S,
    )
    assert match is not None, tbody_id
    return match.group(1)


def _dynamic_empty_cell(html: str, function_name: str) -> str:
    start = html.index(f"function {function_name}")
    next_function = html.find("\nfunction ", start + 1)
    body = html[start : next_function if next_function != -1 else len(html)]
    match = re.search(
        r"tbody\.innerHTML = '<tr class=\"empty-row\"><td colspan=\"\d+\">"
        r"([^<]+)</td></tr>';",
        body,
    )
    assert match is not None, function_name
    return match.group(1)


def test_api_daily_happy_path(tokens_env, monkeypatch):
    token_logs = {
        _day(2): [_entry("gpt-5", 1000)],
        _day(1): [
            _entry("gemini-2.5-flash", 2000),
            _entry("claude-sonnet-4-5", 3000),
        ],
        _day(0): [_entry("claude-sonnet-4-5", 4000)],
    }
    env = tokens_env(token_logs)
    _patch_token_cost(monkeypatch)

    response = env.client.get("/app/tokens/api/daily?days=14")

    assert response.status_code == 200
    payload = response.get_json()
    rows = payload["items"]
    assert payload["total"] == len(rows)
    assert len(rows) == 14
    assert all(set(row) == {"day", "cost", "tokens"} for row in rows)
    assert sorted(rows, key=lambda row: row["day"]) == rows

    by_day = {row["day"]: row for row in rows}
    assert by_day[_day(2)]["tokens"] == 1000
    assert by_day[_day(2)]["cost"] == pytest.approx(0.1)
    assert by_day[_day(1)]["tokens"] == 5000
    assert by_day[_day(1)]["cost"] == pytest.approx(0.5)
    assert by_day[_day(0)]["tokens"] == 4000
    assert by_day[_day(0)]["cost"] == pytest.approx(0.4)

    zero_rows = [row for row in rows if row["day"] not in token_logs]
    assert len(zero_rows) == 11
    assert all(row["cost"] == 0.0 and row["tokens"] == 0 for row in zero_rows)


def test_api_daily_zero_fills_missing_days(tokens_env, monkeypatch):
    token_logs = {
        _day(5): [_entry("gpt-5", 1000)],
        _day(0): [_entry("gemini-2.5-flash", 2000)],
    }
    env = tokens_env(token_logs)
    _patch_token_cost(monkeypatch)

    response = env.client.get("/app/tokens/api/daily?days=7")

    assert response.status_code == 200
    rows = response.get_json()["items"]
    assert [row["day"] for row in rows] == [_day(offset) for offset in range(6, -1, -1)]
    by_day = {row["day"]: row for row in rows}
    assert by_day[_day(5)]["tokens"] == 1000
    assert by_day[_day(0)]["tokens"] == 2000
    for day in {_day(offset) for offset in range(6, -1, -1)} - set(token_logs):
        assert by_day[day] == {"day": day, "cost": 0.0, "tokens": 0}


def test_api_daily_rejects_invalid_days(tokens_env):
    env = tokens_env({})

    cases = {
        "abc": "days must be a number",
        "0": "days must be between 1 and 90",
        "-1": "days must be between 1 and 90",
        "91": "days must be between 1 and 90",
    }
    for days, expected_detail in cases.items():
        response = env.client.get(f"/app/tokens/api/daily?days={days}")
        assert response.status_code == 400
        payload = response.get_json()
        assert payload["reason_code"] == "invalid_request_value"
        assert payload["detail"] == expected_detail


def test_api_daily_cross_month_boundary(tokens_env, monkeypatch):
    fixed_today = real_date(2026, 3, 4)

    class FakeDate(real_date):
        @staticmethod
        def today():
            return fixed_today

    monkeypatch.setattr(token_routes, "date", FakeDate)
    token_logs = {
        _day(offset, fixed_today): [_entry("claude-sonnet-4-5", (7 - offset) * 1000)]
        for offset in range(6, -1, -1)
    }
    env = tokens_env(token_logs)
    _patch_token_cost(monkeypatch)

    response = env.client.get("/app/tokens/api/daily?days=7")

    assert response.status_code == 200
    rows = response.get_json()["items"]
    assert [row["day"] for row in rows] == [
        "20260226",
        "20260227",
        "20260228",
        "20260301",
        "20260302",
        "20260303",
        "20260304",
    ]
    expected_rate = sum(row["cost"] for row in rows) / 7
    assert expected_rate == pytest.approx(0.4)


def test_api_index_reports_nonzero_coverage_and_months(tokens_env, monkeypatch):
    env = tokens_env(
        {
            "20260304": [_entry("gpt-5", 1000)],
            "20260305": [_entry("gpt-5", 2000)],
            "20260401": [_entry("gpt-5", 3000)],
        }
    )
    _patch_token_cost(monkeypatch)

    response = env.client.get("/app/tokens/api/index")

    assert response.status_code == 200
    body = response.get_json()
    assert body["coverage"] == {"start": "20260304", "end": "20260401"}
    assert body["months"]["202603"] == pytest.approx(0.3)
    assert body["months"]["202604"] == pytest.approx(0.3)


def test_api_index_month_total_keeps_two_decimal_contract(tokens_env, monkeypatch):
    env = tokens_env(
        {
            "20260304": [_entry("gpt-5", 1000)],
            "20260305": [_entry("gpt-5", 2000)],
        }
    )
    _patch_token_cost(monkeypatch)

    response = env.client.get("/app/tokens/api/index")

    assert response.status_code == 200
    # Exact equality, not approx: the shared helper sums raw to
    # 0.30000000000000004. The call-site round is what holds the 2dp contract.
    assert response.get_json()["months"]["202603"] == 0.3


def test_api_index_month_totals_match_api_stats(tokens_env, monkeypatch):
    env = tokens_env(
        {
            "20260304": [_entry("gpt-5", 1000)],
            "20260305": [_entry("gpt-5", 2000)],
        }
    )
    _patch_token_cost(monkeypatch)

    response = env.client.get("/app/tokens/api/index")

    assert response.status_code == 200
    body = response.get_json()
    for month, total in body["months"].items():
        month_response = env.client.get(f"/app/tokens/api/stats/{month}")
        assert month_response.status_code == 200
        assert total == pytest.approx(sum(month_response.get_json().values()))


def test_api_index_empty_journal(tokens_env):
    env = tokens_env({})

    response = env.client.get("/app/tokens/api/index")

    assert response.status_code == 200
    assert response.get_json() == {"coverage": None, "months": {}}


def test_api_index_is_read_only(tokens_env, monkeypatch):
    env = tokens_env({"20260304": [_entry("gpt-5", 1000)]})
    _patch_token_cost(monkeypatch)
    before = _tokens_snapshot(env.journal)

    response = env.client.get("/app/tokens/api/index")

    assert response.status_code == 200
    assert _tokens_snapshot(env.journal) == before


def test_tokens_page_serves_spa_shell(tokens_env):
    env = tokens_env({})

    response = env.client.get("/app/tokens/20260304")

    assert response.status_code == 200
    assert b'data-solstone-shell="spa"' in response.data


def test_tokens_index_redirects_to_shell(tokens_env):
    env = tokens_env({})

    response = env.client.get("/app/tokens/", follow_redirects=True)

    assert response.status_code == 200
    assert b'data-solstone-shell="spa"' in response.data


def test_tokens_day_guard_still_404s(tokens_env):
    env = tokens_env({})

    response = env.client.get("/app/tokens/notaday")

    assert response.status_code == 404


def test_tokens_workspace_contains_client_copy_and_static_labels(tokens_env):
    env = tokens_env({})

    response = env.client.get("/app/tokens/workspace")

    assert response.status_code == 200
    html = response.get_data(as_text=True)
    assert 'TOKENS_TILE_COST_LABEL: "today\'s cost"' in html
    assert 'TOKENS_TILE_TOKENS_LABEL: "today\'s tokens"' in html
    assert 'TOKENS_TILE_RUN_RATE_LABEL: "7-day run rate"' in html
    assert 'TOKENS_TILE_TOP_DRIVER_LABEL: "today\'s biggest cost"' in html
    assert 'data-tokens-copy-key="TOKENS_TILE_COST_LABEL"' in html
    assert "window.TOKENS_COPY = TOKENS_COPY" in html


def test_tokens_static_and_dynamic_empty_rows_share_copy(tokens_env):
    env = tokens_env({})

    response = env.client.get("/app/tokens/workspace")

    assert response.status_code == 200
    html = response.get_data(as_text=True)
    empty_rows = {
        "provider-body": "renderProviderTable",
        "model-body": "renderModelTable",
        "token-type-body": "renderTokenTypeTable",
        "segment-body": "renderSegmentTable",
    }
    for tbody_id, function_name in empty_rows.items():
        assert _static_empty_cell(html, tbody_id) == _dynamic_empty_cell(
            html,
            function_name,
        )

    assert _static_empty_cell(html, "provider-body") == "no data for this day"
    context_empty = _dynamic_empty_cell(html, "renderContextTable")
    assert context_empty == context_empty.lower()
    assert "No data for this day" not in html
    assert "No matching contexts" not in html
    assert "No segment data for this day" not in html


def test_tokens_page_renders_collapsed_details_for_all_breakdowns(tokens_env):
    env = tokens_env({})

    response = env.client.get("/app/tokens/workspace")

    assert response.status_code == 200
    html = response.get_data(as_text=True)
    tags = re.findall(r'<details[^>]*data-disclosure="([\w-]+)"[^>]*>', html)
    assert set(tags) == {"provider", "model", "token-type", "context", "segment"}
    assert len(tags) == 5
    detail_tags = re.findall(r'<details[^>]*data-disclosure="[\w-]+"[^>]*>', html)
    assert all(" open" not in tag for tag in detail_tags)
