"""Test ECB source."""

from pathlib import Path
from unittest.mock import MagicMock, patch

import pandas as pd
import pytest
import requests

from viadot.sources.ecb import ECBExchangeRates


@pytest.fixture
def sample_ecb_response():
    """Load sample ECB API response for testing."""
    response_path = Path(__file__).parent / "test_ecb_response.xml"
    with response_path.open(encoding="utf-8") as f:
        return f.read()


@pytest.fixture
def ecb_instance():
    """Create ECBExchangeRates instance."""
    return ECBExchangeRates()


def test_init(ecb_instance):
    """Test ECBExchangeRates initialization."""
    assert ecb_instance.credentials is None
    assert (
        ecb_instance.URL
        == "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml"
    )


@patch("viadot.sources.ecb.handle_api_response")
def test_fetch_data_success(
    mock_handle_api_response, ecb_instance, sample_ecb_response
):
    """Test successful data fetching."""
    # Setup mock response
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.text = sample_ecb_response
    mock_handle_api_response.return_value = mock_response

    # Call fetch_data (no url parameter needed)
    data = ecb_instance.fetch_data()

    # Verify the data was returned
    assert data == sample_ecb_response

    # Verify handle_api_response was called correctly with hardcoded URL
    mock_handle_api_response.assert_called_once()
    call_args = mock_handle_api_response.call_args
    assert call_args[1]["url"] == ecb_instance.URL
    assert call_args[1]["params"] is None
    assert call_args[1]["method"] == "GET"


@patch("viadot.sources.ecb.handle_api_response")
def test_fetch_data_request_exception(mock_handle_api_response, ecb_instance):
    """Test fetch_data with request exception."""
    from viadot.exceptions import APIError

    mock_handle_api_response.side_effect = requests.exceptions.RequestException(
        "Connection failed"
    )

    with pytest.raises(APIError):
        ecb_instance.fetch_data()


def test_parse_xml(ecb_instance, sample_ecb_response):
    """Test XML parsing."""
    df = ecb_instance._parse_xml(sample_ecb_response)

    assert isinstance(df, pd.DataFrame)
    assert not df.empty
    assert "time" in df.columns
    assert "currency" in df.columns
    assert "rate" in df.columns

    # Check that we have data
    assert len(df) > 0

    # Check that time is consistent
    assert df["time"].nunique() == 1
    assert str(df["time"][0].date()) == "2025-12-30"

    # Check that currency values are strings
    assert df["currency"].dtype == "object"

    # Check that rate values are numeric
    assert pd.api.types.is_numeric_dtype(df["rate"])


def test_parse_xml_invalid_xml(ecb_instance):
    """Test parse_xml with invalid XML."""
    # Use XML that doesn't match ECB structure
    # This will parse successfully but fail structure validation
    invalid_xml = "<invalid>xml</invalid>"
    with pytest.raises(ValueError, match="Could not find time attribute"):
        ecb_instance._parse_xml(invalid_xml)


def test_parse_xml_missing_time(ecb_instance):
    """Test parse_xml with XML missing time attribute."""
    xml_without_time = """<?xml version="1.0" encoding="UTF-8"?>
    <gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
        <Cube>
            <Cube currency='USD' rate='1.1757'/>
        </Cube>
    </gesmes:Envelope>"""
    with pytest.raises(ValueError, match="Could not find time attribute"):
        ecb_instance._parse_xml(xml_without_time)


def test_parse_xml_empty_data(ecb_instance):
    """Test parse_xml with XML containing no exchange rates."""
    xml_empty = """<?xml version="1.0" encoding="UTF-8"?>
    <gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
        <Cube>
            <Cube time='2025-12-30'>
            </Cube>
        </Cube>
    </gesmes:Envelope>"""
    df = ecb_instance._parse_xml(xml_empty)
    assert df.empty
    assert list(df.columns) == ["time", "currency", "rate"]


@patch("viadot.sources.ecb.handle_api_response")
def test_to_df_success(mock_handle_api_response, ecb_instance, sample_ecb_response):
    """Test successful to_df conversion."""
    # Setup mock response
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.text = sample_ecb_response
    mock_handle_api_response.return_value = mock_response

    # Call to_df (no url parameter needed)
    df = ecb_instance.to_df()

    assert isinstance(df, pd.DataFrame)
    assert not df.empty
    assert "time" in df.columns
    assert "currency" in df.columns
    assert "rate" in df.columns


@patch("viadot.sources.ecb.handle_api_response")
def test_to_df_empty_result_warn(mock_handle_api_response, ecb_instance, caplog):
    """Test to_df with empty result and warn if_empty."""
    xml_empty = """<?xml version="1.0" encoding="UTF-8"?>
    <gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
        <Cube>
            <Cube time='2025-12-30'>
            </Cube>
        </Cube>
    </gesmes:Envelope>"""

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.text = xml_empty
    mock_handle_api_response.return_value = mock_response

    with caplog.at_level("WARNING"):
        df = ecb_instance.to_df(if_empty="warn")

    assert df.empty

    assert "No exchange rate data found in XML response." in caplog.text
    assert "No exchange rates found in the response." in caplog.text
    assert "The query produced no data." in caplog.text


@patch("viadot.sources.ecb.handle_api_response")
def test_to_df_empty_result_skip(mock_handle_api_response, ecb_instance):
    """Test to_df with empty result and skip if_empty."""
    from viadot.signals import SKIP

    xml_empty = """<?xml version="1.0" encoding="UTF-8"?>
    <gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
        <Cube>
            <Cube time='2025-12-30'>
            </Cube>
        </Cube>
    </gesmes:Envelope>"""

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.text = xml_empty
    mock_handle_api_response.return_value = mock_response

    with pytest.raises(SKIP):
        ecb_instance.to_df(if_empty="skip")


@patch("viadot.sources.ecb.handle_api_response")
def test_to_df_empty_result_fail(mock_handle_api_response, ecb_instance):
    """Test to_df with empty result and fail if_empty."""
    xml_empty = """<?xml version="1.0" encoding="UTF-8"?>
    <gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
        <Cube>
            <Cube time='2025-12-30'>
            </Cube>
        </Cube>
    </gesmes:Envelope>"""

    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.text = xml_empty
    mock_handle_api_response.return_value = mock_response

    with pytest.raises(ValueError, match="The query produced no data"):
        ecb_instance.to_df(if_empty="fail")


@patch("viadot.sources.ecb.handle_api_response")
def test_to_df_with_tests_validation(
    mock_handle_api_response, ecb_instance, sample_ecb_response
):
    """Test to_df with tests validation."""
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.text = sample_ecb_response
    mock_handle_api_response.return_value = mock_response

    test_config = {"column_tests": {"currency": {"not_null": True}}}

    with patch("viadot.sources.ecb.validate") as mock_validate:
        df = ecb_instance.to_df(tests=test_config)

        mock_validate.assert_called_once()
        args, kwargs = mock_validate.call_args
        assert kwargs["df"] is df
        assert kwargs["tests"] == test_config


@patch("viadot.sources.ecb.handle_api_response")
def test_full_workflow(mock_handle_api_response, ecb_instance, sample_ecb_response):
    """Test complete workflow from fetch to DataFrame."""
    # Setup mock response
    mock_response = MagicMock()
    mock_response.status_code = 200
    mock_response.text = sample_ecb_response
    mock_handle_api_response.return_value = mock_response

    # Fetch and convert to DataFrame (no url parameter needed)
    df = ecb_instance.to_df()

    # Verify results
    assert isinstance(df, pd.DataFrame)
    assert not df.empty
    assert "time" in df.columns
    assert "currency" in df.columns
    assert "rate" in df.columns

    # Verify we have multiple currencies
    assert df["currency"].nunique() > 1
