"""Tests for mixlift_prepare MCP tool — CSV normalization to wide format."""

import json
from pathlib import Path

import numpy as np
import pandas as pd
import pytest

from mixlift_mcp.server import mixlift_prepare


# --- Fixtures ---


@pytest.fixture
def wide_csv(tmp_path):
    """Already in MixLift wide format (date, *_spend, revenue)."""
    n_weeks = 30
    dates = pd.date_range("2023-06-01", periods=n_weeks, freq="W-MON")
    rng = np.random.default_rng(42)
    df = pd.DataFrame({
        "date": dates,
        "google_spend": rng.uniform(1000, 5000, n_weeks),
        "meta_spend": rng.uniform(2000, 8000, n_weeks),
        "revenue": rng.uniform(30000, 80000, n_weeks),
    })
    path = tmp_path / "already_wide.csv"
    df.to_csv(path, index=False)
    return path


@pytest.fixture
def long_csv(tmp_path):
    """Long/tidy format: one row per date × channel."""
    dates = pd.date_range("2023-01-01", periods=30, freq="W-MON")
    rows = []
    rng = np.random.default_rng(42)
    for d in dates:
        for channel in ["google", "meta", "tiktok"]:
            rows.append({
                "date": d,
                "channel": channel,
                "spend": rng.uniform(500, 5000),
                "revenue": rng.uniform(10000, 30000),
            })
    df = pd.DataFrame(rows)
    path = tmp_path / "long_format.csv"
    df.to_csv(path, index=False)
    return path


@pytest.fixture
def campaign_csv(tmp_path):
    """Campaign-level rows with currency strings and a platform column."""
    dates = pd.date_range("2023-01-01", periods=26, freq="W-MON")
    rows = []
    rng = np.random.default_rng(99)
    for d in dates:
        for platform in ["Facebook", "Google Ads"]:
            spend = rng.uniform(1000, 8000)
            revenue = rng.uniform(5000, 25000)
            rows.append({
                "report_date": d,
                "platform": platform,
                "amount_spent": f"${spend:,.2f}",
                "total_revenue": f"${revenue:,.2f}",
            })
    df = pd.DataFrame(rows)
    path = tmp_path / "campaign_data.csv"
    df.to_csv(path, index=False)
    return path


@pytest.fixture
def roi_csv(tmp_path):
    """CSV with ROI column instead of revenue."""
    dates = pd.date_range("2023-01-01", periods=30, freq="W-MON")
    rows = []
    rng = np.random.default_rng(77)
    for d in dates:
        for channel in ["search", "social"]:
            spend = rng.uniform(1000, 5000)
            rows.append({
                "date": d,
                "channel": channel,
                "cost": spend,
                "roi": rng.uniform(0.5, 3.0),  # ratio, not percentage
            })
    df = pd.DataFrame(rows)
    path = tmp_path / "roi_data.csv"
    df.to_csv(path, index=False)
    return path


@pytest.fixture
def single_channel_csv(tmp_path):
    """Only one channel — should fail minimum channel requirement."""
    dates = pd.date_range("2023-01-01", periods=30, freq="W-MON")
    rng = np.random.default_rng(42)
    df = pd.DataFrame({
        "date": dates,
        "channel": "google",
        "spend": rng.uniform(1000, 5000, 30),
        "revenue": rng.uniform(10000, 30000, 30),
    })
    path = tmp_path / "single_channel.csv"
    df.to_csv(path, index=False)
    return path


@pytest.fixture
def short_csv(tmp_path):
    """Only 8 weeks — below 13-week minimum."""
    dates = pd.date_range("2023-01-01", periods=8, freq="W-MON")
    rows = []
    rng = np.random.default_rng(42)
    for d in dates:
        for ch in ["google", "meta"]:
            rows.append({"date": d, "channel": ch, "spend": rng.uniform(1000, 5000),
                         "revenue": rng.uniform(10000, 30000)})
    df = pd.DataFrame(rows)
    path = tmp_path / "short.csv"
    df.to_csv(path, index=False)
    return path


@pytest.fixture
def no_channel_csv(tmp_path):
    """CSV with no detectable channel column."""
    dates = pd.date_range("2023-01-01", periods=30, freq="W-MON")
    rng = np.random.default_rng(42)
    df = pd.DataFrame({
        "date": dates,
        "amount": rng.uniform(1000, 5000, 30),
        "revenue": rng.uniform(10000, 30000, 30),
    })
    path = tmp_path / "no_channel.csv"
    df.to_csv(path, index=False)
    return path


# --- Tests ---


class TestPrepareAlreadyWide:
    """When CSV is already in wide format, return immediately."""

    @pytest.mark.asyncio
    async def test_already_wide_passthrough(self, wide_csv):
        raw = await mixlift_prepare(str(wide_csv))
        result = json.loads(raw)

        assert result["status"] == "already_wide"
        assert result["output_path"] == str(wide_csv)
        assert "google" in result["channels"]
        assert "meta" in result["channels"]

    @pytest.mark.asyncio
    async def test_already_wide_message(self, wide_csv):
        result = json.loads(await mixlift_prepare(str(wide_csv)))
        assert "already" in result["message"].lower()


class TestPrepareLongToWide:
    """Convert long/tidy format to wide format."""

    @pytest.mark.asyncio
    async def test_long_format_conversion(self, long_csv):
        raw = await mixlift_prepare(str(long_csv))
        result = json.loads(raw)

        assert result["status"] == "success"
        assert len(result["channels"]) == 3
        assert "google" in result["channels"]
        assert "meta" in result["channels"]
        assert "tiktok" in result["channels"]

    @pytest.mark.asyncio
    async def test_long_format_output_readable(self, long_csv):
        result = json.loads(await mixlift_prepare(str(long_csv)))
        out_path = result["output_path"]

        df = pd.read_csv(out_path)
        assert "date" in df.columns
        assert "revenue" in df.columns
        spend_cols = [c for c in df.columns if c.endswith("_spend")]
        assert len(spend_cols) == 3

    @pytest.mark.asyncio
    async def test_long_format_row_count(self, long_csv):
        result = json.loads(await mixlift_prepare(str(long_csv)))
        assert result["rows"] == 30  # 30 weeks aggregated


class TestPrepareCurrencyParsing:
    """Handle currency strings like $16,174.00."""

    @pytest.mark.asyncio
    async def test_currency_strings_parsed(self, campaign_csv):
        raw = await mixlift_prepare(str(campaign_csv))
        result = json.loads(raw)

        assert result["status"] == "success"
        out_df = pd.read_csv(result["output_path"])
        spend_cols = [c for c in out_df.columns if c.endswith("_spend")]
        # All spend values should be positive numbers, not strings
        for col in spend_cols:
            assert out_df[col].dtype in (np.float64, np.int64)
            assert (out_df[col] >= 0).all()


class TestPrepareColumnAutoDetection:
    """Verify heuristic column detection and explicit overrides."""

    @pytest.mark.asyncio
    async def test_detects_platform_column(self, campaign_csv):
        result = json.loads(await mixlift_prepare(str(campaign_csv)))
        assert result["status"] == "success"
        assert result["columns_detected"]["channel"] == "platform"

    @pytest.mark.asyncio
    async def test_detects_report_date(self, campaign_csv):
        result = json.loads(await mixlift_prepare(str(campaign_csv)))
        assert result["columns_detected"]["date"] == "report_date"

    @pytest.mark.asyncio
    async def test_explicit_override(self, long_csv):
        result = json.loads(await mixlift_prepare(
            str(long_csv), date_col="date", channel_col="channel", spend_col="spend"
        ))
        assert result["status"] == "success"
        assert result["columns_detected"]["date"] == "date"
        assert result["columns_detected"]["channel"] == "channel"
        assert result["columns_detected"]["spend"] == "spend"

    @pytest.mark.asyncio
    async def test_missing_channel_col_error(self, no_channel_csv):
        result = json.loads(await mixlift_prepare(str(no_channel_csv)))
        assert result["status"] == "error"
        assert "channel" in result["message"].lower()
        assert "columns_found" in result


class TestPrepareROIDerivation:
    """Revenue derived from spend * ROI when no direct revenue column."""

    @pytest.mark.asyncio
    async def test_roi_to_revenue(self, roi_csv):
        result = json.loads(await mixlift_prepare(str(roi_csv)))
        assert result["status"] == "success"

        out_df = pd.read_csv(result["output_path"])
        assert "revenue" in out_df.columns
        assert (out_df["revenue"] > 0).all()


class TestPrepareErrorCases:
    """Edge cases and validation errors."""

    @pytest.mark.asyncio
    async def test_file_not_found(self, tmp_path):
        result = json.loads(await mixlift_prepare(str(tmp_path / "nope.csv")))
        assert result["status"] == "error"

    @pytest.mark.asyncio
    async def test_single_channel_rejected(self, single_channel_csv):
        result = json.loads(await mixlift_prepare(str(single_channel_csv)))
        assert result["status"] == "error"
        assert "2 channel" in result["message"].lower() or "1 channel" in result["message"].lower()

    @pytest.mark.asyncio
    async def test_too_few_weeks_rejected(self, short_csv):
        result = json.loads(await mixlift_prepare(str(short_csv)))
        assert result["status"] == "error"
        assert "week" in result["message"].lower() or "13" in result["message"]

    @pytest.mark.asyncio
    async def test_non_csv_rejected(self, tmp_path):
        bad = tmp_path / "data.xlsx"
        bad.write_text("not a csv")
        result = json.loads(await mixlift_prepare(str(bad)))
        assert result["status"] == "error"

    @pytest.mark.asyncio
    async def test_zero_spend_channels_dropped(self, tmp_path):
        """Channels with zero total spend should be excluded."""
        dates = pd.date_range("2023-01-01", periods=30, freq="W-MON")
        rows = []
        rng = np.random.default_rng(42)
        for d in dates:
            for ch, spend in [("google", rng.uniform(1000, 5000)),
                              ("meta", rng.uniform(1000, 5000)),
                              ("dead_channel", 0.0)]:
                rows.append({"date": d, "channel": ch, "spend": spend,
                             "revenue": rng.uniform(10000, 30000)})
        df = pd.DataFrame(rows)
        path = tmp_path / "zero_channel.csv"
        df.to_csv(path, index=False)

        result = json.loads(await mixlift_prepare(str(path)))
        assert result["status"] == "success"
        assert "dead_channel" not in result["channels"]
        assert len(result["channels"]) == 2
