from decimal import Decimal
from unittest.mock import MagicMock, patch

import pytest
from flask import Flask, request

from controllers.console.app.statistic import (
    AverageResponseTimeStatistic,
    AverageSessionInteractionStatistic,
    DailyConversationStatistic,
    DailyMessageStatistic,
    DailyTerminalsStatistic,
    DailyTokenCostStatistic,
    TokensPerSecondStatistic,
    UserSatisfactionRateStatistic,
)
from models import App, AppMode


@pytest.fixture
def app():
    flask_app = Flask(__name__)
    flask_app.config["TESTING"] = True
    return flask_app


@pytest.fixture
def mock_account():
    from models.account import Account, AccountStatus

    account = MagicMock(spec=Account)
    account.id = "user_123"
    account.timezone = "UTC"
    account.status = AccountStatus.ACTIVE
    account.is_admin_or_owner = True
    account.current_tenant.current_role = "owner"
    account.has_edit_permission = True
    return account


@pytest.fixture
def mock_app_model():
    app_model = MagicMock(spec=App)
    app_model.id = "app_123"
    app_model.mode = AppMode.CHAT
    app_model.tenant_id = "tenant_123"
    return app_model


@pytest.fixture(autouse=True)
def mock_csrf():
    with patch("libs.login.check_csrf_token") as mock:
        yield mock


def setup_test_context(
    test_app, endpoint_class, route_path, mock_account, mock_app_model, mock_rs, mock_parse_ret=(None, None)
):
    with (
        patch("controllers.console.app.statistic.db") as mock_db_stat,
        patch("controllers.console.app.wraps.db") as mock_db_wraps,
        patch("controllers.console.wraps.db", mock_db_wraps),
        patch(
            "controllers.console.app.statistic.current_account_with_tenant", return_value=(mock_account, "tenant_123")
        ),
        patch("controllers.console.app.wraps.current_account_with_tenant", return_value=(mock_account, "tenant_123")),
        patch("controllers.console.wraps.current_account_with_tenant", return_value=(mock_account, "tenant_123")),
    ):
        mock_conn = MagicMock()
        mock_conn.execute.return_value = mock_rs

        mock_begin = MagicMock()
        mock_begin.__enter__.return_value = mock_conn
        mock_db_stat.engine.begin.return_value = mock_begin

        mock_query = MagicMock()
        mock_query.filter.return_value.first.return_value = mock_app_model
        mock_query.filter.return_value.filter.return_value.first.return_value = mock_app_model
        mock_query.where.return_value.first.return_value = mock_app_model
        mock_query.where.return_value.where.return_value.first.return_value = mock_app_model
        mock_db_wraps.session.query.return_value = mock_query

        with patch("libs.login._get_user", return_value=mock_account), patch("flask_login.current_user", mock_account):
            with test_app.test_request_context(route_path, method="GET"):
                request.view_args = {"app_id": "app_123"}
                api_instance = endpoint_class()
                response = api_instance.get(app_id="app_123")
                return response


class TestStatisticEndpoints:
    def test_daily_message_statistic(self, app, mock_account, mock_app_model):
        mock_row = MagicMock()
        mock_row.date = "2023-01-01"
        mock_row.message_count = 10
        mock_row.interactions = Decimal(0)

        with patch("controllers.console.app.statistic.parse_time_range", return_value=(None, None)):
            response = setup_test_context(
                app,
                DailyMessageStatistic,
                "/apps/app_123/statistics/daily-messages?start=2023-01-01 00:00&end=2023-01-02 00:00",
                mock_account,
                mock_app_model,
                [mock_row],
            )
        assert response.status_code == 200
        assert response.json["data"][0]["message_count"] == 10

    def test_daily_conversation_statistic(self, app, mock_account, mock_app_model):
        mock_row = MagicMock()
        mock_row.date = "2023-01-01"
        mock_row.conversation_count = 5
        mock_row.interactions = Decimal(0)

        with patch("controllers.console.app.statistic.parse_time_range", return_value=(None, None)):
            response = setup_test_context(
                app,
                DailyConversationStatistic,
                "/apps/app_123/statistics/daily-conversations",
                mock_account,
                mock_app_model,
                [mock_row],
            )
        assert response.status_code == 200
        assert response.json["data"][0]["conversation_count"] == 5

    def test_daily_terminals_statistic(self, app, mock_account, mock_app_model):
        mock_row = MagicMock()
        mock_row.date = "2023-01-01"
        mock_row.terminal_count = 2
        mock_row.interactions = Decimal(0)

        with patch("controllers.console.app.statistic.parse_time_range", return_value=(None, None)):
            response = setup_test_context(
                app,
                DailyTerminalsStatistic,
                "/apps/app_123/statistics/daily-end-users",
                mock_account,
                mock_app_model,
                [mock_row],
            )
        assert response.status_code == 200
        assert response.json["data"][0]["terminal_count"] == 2

    def test_daily_token_cost_statistic(self, app, mock_account, mock_app_model):
        mock_row = MagicMock()
        mock_row.date = "2023-01-01"
        mock_row.token_count = 100
        mock_row.total_price = Decimal("0.02")
        mock_row.interactions = Decimal(0)

        with patch("controllers.console.app.statistic.parse_time_range", return_value=(None, None)):
            response = setup_test_context(
                app,
                DailyTokenCostStatistic,
                "/apps/app_123/statistics/token-costs",
                mock_account,
                mock_app_model,
                [mock_row],
            )
        assert response.status_code == 200
        assert response.json["data"][0]["token_count"] == 100
        assert response.json["data"][0]["total_price"] == "0.02"

    def test_average_session_interaction_statistic(self, app, mock_account, mock_app_model):
        mock_row = MagicMock()
        mock_row.date = "2023-01-01"
        mock_row.interactions = Decimal("3.523")

        with patch("controllers.console.app.statistic.parse_time_range", return_value=(None, None)):
            response = setup_test_context(
                app,
                AverageSessionInteractionStatistic,
                "/apps/app_123/statistics/average-session-interactions",
                mock_account,
                mock_app_model,
                [mock_row],
            )
        assert response.status_code == 200
        assert response.json["data"][0]["interactions"] == 3.52

    def test_user_satisfaction_rate_statistic(self, app, mock_account, mock_app_model):
        mock_row = MagicMock()
        mock_row.date = "2023-01-01"
        mock_row.message_count = 100
        mock_row.feedback_count = 10
        mock_row.interactions = Decimal(0)

        with patch("controllers.console.app.statistic.parse_time_range", return_value=(None, None)):
            response = setup_test_context(
                app,
                UserSatisfactionRateStatistic,
                "/apps/app_123/statistics/user-satisfaction-rate",
                mock_account,
                mock_app_model,
                [mock_row],
            )
        assert response.status_code == 200
        assert response.json["data"][0]["rate"] == 100.0

    def test_average_response_time_statistic(self, app, mock_account, mock_app_model):
        mock_app_model.mode = AppMode.COMPLETION
        mock_row = MagicMock()
        mock_row.date = "2023-01-01"
        mock_row.latency = 1.234
        mock_row.interactions = Decimal(0)

        with patch("controllers.console.app.statistic.parse_time_range", return_value=(None, None)):
            response = setup_test_context(
                app,
                AverageResponseTimeStatistic,
                "/apps/app_123/statistics/average-response-time",
                mock_account,
                mock_app_model,
                [mock_row],
            )
        assert response.status_code == 200
        assert response.json["data"][0]["latency"] == 1234.0

    def test_tokens_per_second_statistic(self, app, mock_account, mock_app_model):
        mock_row = MagicMock()
        mock_row.date = "2023-01-01"
        mock_row.tokens_per_second = 15.5
        mock_row.interactions = Decimal(0)

        with patch("controllers.console.app.statistic.parse_time_range", return_value=(None, None)):
            response = setup_test_context(
                app,
                TokensPerSecondStatistic,
                "/apps/app_123/statistics/tokens-per-second",
                mock_account,
                mock_app_model,
                [mock_row],
            )
        assert response.status_code == 200
        assert response.json["data"][0]["tps"] == 15.5

    @patch("controllers.console.app.statistic.parse_time_range")
    def test_invalid_time_range(self, mock_parse, app, mock_account, mock_app_model):
        mock_parse.side_effect = ValueError("Invalid time")

        from werkzeug.exceptions import BadRequest

        with pytest.raises(BadRequest):
            setup_test_context(
                app,
                DailyMessageStatistic,
                "/apps/app_123/statistics/daily-messages?start=invalid&end=invalid",
                mock_account,
                mock_app_model,
                [],
            )

    @patch("controllers.console.app.statistic.parse_time_range")
    def test_time_range_params_passed(self, mock_parse, app, mock_account, mock_app_model):
        import datetime

        start = datetime.datetime.now()
        end = datetime.datetime.now()
        mock_parse.return_value = (start, end)

        response = setup_test_context(
            app,
            DailyMessageStatistic,
            "/apps/app_123/statistics/daily-messages?start=something&end=something",
            mock_account,
            mock_app_model,
            [],
        )
        assert response.status_code == 200
        mock_parse.assert_called_once()
