"""Enterprise dashboard designer: new widget types and display options,
dashboard-wide filters (dashboard.parameters), date ranges on the audit
columns, and AI-suggested widgets (app/crud/dashboard_ai.py)."""

import json
from datetime import datetime, timedelta, timezone

import pytest

from app.routers import dashboards as dashboards_router


async def _dashboard(client, auth_headers, parameters=None):
    resp = await client.post("/dashboards", headers=auth_headers, json={"name": "Ops overview", "parameters": parameters or []})
    assert resp.status_code == 201, resp.text
    return resp.json()


async def _master(client, auth_headers):
    master = await client.post(
        "/masters",
        headers=auth_headers,
        json={"name": "Tickets", "field_schema": [{"name": "status", "type": "varchar"}, {"name": "district", "type": "varchar"}, {"name": "amount", "type": "integer"}]},
    )
    assert master.status_code == 201, master.text
    for status, district, amount in (("open", "Kochi", 10), ("closed", "Kochi", 20), ("open", "Thrissur", 5)):
        rec = await client.post(f"/masters/{master.json()['id']}/records", headers=auth_headers, json={"field_values": {"status": status, "district": district, "amount": amount}})
        assert rec.status_code == 201, rec.text
    return master.json()["id"]


@pytest.mark.parametrize("wtype", ["area_chart", "combo_chart", "funnel_chart", "radar_chart", "treemap", "gauge", "leaderboard", "text"])
async def test_new_widget_types_and_options_round_trip(client, auth_headers, wtype):
    dash = await _dashboard(client, auth_headers)
    config = {
        "title": "Tickets", "subtitle": "This quarter", "col_span": 4, "height": "tall", "color_scheme": "ocean",
        "show_legend": False, "show_values": True, "orientation": "horizontal", "stacked": True, "curve": "step",
        "sort": "value_desc", "top_n": 5, "number_format": {"style": "currency", "decimals": 0, "compact": True},
        "target": 100, "max_value": 150, "trend_field": "created_at", "trend_period": "month", "sparkline": True,
        "text": "Notes", "series": [{"aggregate": "count", "label": "Count", "chart": "line"}],
    }
    resp = await client.post(f"/dashboards/{dash['id']}/widgets", headers=auth_headers, json={"type": wtype, "data_source": "none" if wtype == "text" else "master:x", "color": "#6366f1", "config": config})
    assert resp.status_code == 201, resp.text
    saved = resp.json()["config"]
    for key in ("col_span", "height", "color_scheme", "show_values", "orientation", "stacked", "curve", "sort", "top_n", "target", "trend_period", "sparkline", "text"):
        assert saved[key] == config[key], key
    assert saved["number_format"]["style"] == "currency" and saved["series"][0]["chart"] == "line"

    bad = await client.post(f"/dashboards/{dash['id']}/widgets", headers=auth_headers, json={"type": wtype, "data_source": "none", "color": "#000", "config": {"col_span": 9}})
    assert bad.status_code == 422


async def test_dashboard_wide_filters_are_saved(client, auth_headers):
    params = [
        {"name": "period", "kind": "date_range", "field": "created_at", "label": "Created", "default": "this_month"},
        {"name": "owner", "kind": "user", "field": "created_by", "label": "Created by", "default": "me"},
    ]
    dash = await _dashboard(client, auth_headers, params)
    assert dash["parameters"] == params
    legacy = await _dashboard(client, auth_headers, [{"name": "range"}])  # the old {name, kind} shape still loads
    assert legacy["parameters"][0]["kind"] == "date_range" and legacy["parameters"][0]["field"] == "created_at"


async def test_created_at_date_range_includes_the_whole_end_day(client, auth_headers):
    master_id = await _master(client, auth_headers)
    utc_today = datetime.now(timezone.utc).date()  # records are stamped in UTC
    today = utc_today.isoformat()
    query = {"source_type": "master", "source_id": master_id, "columns": [], "limit": 50}
    same_day = await client.post("/datasets/query", headers=auth_headers, json={**query, "filters": [{"field": "created_at", "operator": "between", "value": [today, today]}]})
    assert same_day.status_code == 200 and same_day.json()["total"] == 3
    yesterday = (utc_today - timedelta(days=1)).isoformat()
    before = await client.post("/datasets/query", headers=auth_headers, json={**query, "filters": [{"field": "created_at", "operator": "between", "value": [yesterday, yesterday]}]})
    assert before.json()["total"] == 0
    mine_today = await client.post(
        "/datasets/query-series",
        headers=auth_headers,
        json={**{k: v for k, v in query.items() if k != "columns"}, "x_field": "status", "x_bucket": "none", "series": [{"aggregate": "count", "label": "n"}],
              "filters": [{"field": "created_by", "operator": "equals", "value": "{{current_user.id}}"}, {"field": "updated_at", "operator": "between", "value": [today, today]}]},
    )
    assert mine_today.status_code == 200, mine_today.text
    assert {r["x"]: r["n"] for r in mine_today.json()["rows"]} == {"open": 2, "closed": 1}


async def test_ai_suggestions_are_validated_against_real_fields(client, auth_headers, monkeypatch):
    await _master(client, auth_headers)
    answer = {
        "message": "Ticket overview",
        "widgets": [
            {"type": "kpi_card", "title": "Open tickets", "source_type": "master", "source_name": "tickets", "value_aggregate": "count",
             "filters": [{"field": "status", "operator": "equals", "value": "open"}, {"field": "made_up", "operator": "equals", "value": 1}], "trend_period": "month"},
            {"type": "bar_chart", "title": "By district", "source_type": "master", "source_name": "Tickets", "x_field": "district",
             "series": [{"field": "amount", "aggregate": "sum", "label": "Amount"}, {"field": "nope", "aggregate": "sum"}]},
            {"type": "line_chart", "title": "Trend", "source_type": "master", "source_name": "Tickets", "x_field": "created_at", "series": []},
            {"type": "leaderboard", "title": "Top districts", "source_type": "master", "source_name": "Tickets", "slice_field": "district", "value_field": "amount", "value_aggregate": "sum"},
            {"type": "pie_chart", "title": "Invented", "source_type": "master", "source_name": "Tickets", "slice_field": "ghost"},
            {"type": "bar_chart", "title": "Wrong table", "source_type": "master", "source_name": "Nope", "x_field": "status"},
            {"type": "kpi_card", "title": "My tickets", "source_type": "master", "source_name": "Tickets",
             "filters": [{"field": "created_by", "operator": "equals", "value": "{{current_user.id}}"}]},
        ],
    }

    async def fake_chat(messages):
        assert "Tickets" in messages[0]["content"] and "created_by" in messages[0]["content"]
        return "```json\n" + json.dumps(answer) + "\n```"

    monkeypatch.setattr(dashboards_router, "chat_completion", fake_chat)
    resp = await client.post("/dashboards/ai-widgets", headers=auth_headers, json={"prompt": "ticket overview"})
    assert resp.status_code == 200, resp.text
    widgets = resp.json()["widgets"]
    assert [w["config"]["title"] for w in widgets] == ["Open tickets", "By district", "Trend", "Top districts", "My tickets"]
    kpi, bar, trend, board, mine = widgets
    assert kpi["config"]["filters"] == [{"field": "status", "operator": "equals", "value": "open"}] and kpi["config"]["trend_period"] == "month"
    assert kpi["size"] == "small" and kpi["data_source"].startswith("master:")
    assert [s["label"] for s in bar["config"]["series"]] == ["Amount"]
    assert trend["config"]["x_bucket"] == "month" and trend["config"]["series"][0]["aggregate"] == "count"
    assert board["config"]["sort"] == "value_desc" and board["config"]["top_n"] == 10 and board["config"]["value_aggregate"] == "sum"
    assert mine["config"]["filters"][0]["value"] == "{{current_user.id}}"

    async def broken_chat(messages):
        return "not json"

    monkeypatch.setattr(dashboards_router, "chat_completion", broken_chat)
    bad = await client.post("/dashboards/ai-widgets", headers=auth_headers, json={"prompt": "anything"})
    assert bad.status_code == 200 and bad.json()["widgets"] == [] and bad.json()["error"]
