import aiohttp
import pytest
from aioresponses import aioresponses
import pytest_asyncio
from watergate_local_api import WatergateLocalApiClient, WatergateApiException

@pytest_asyncio.fixture
async def client():
    # Create and initialize the client within an async context
    client = WatergateLocalApiClient(base_url="http://testserver")
    yield client
    # Ensure proper cleanup after each test
    await client.async_close()

@pytest.mark.asyncio
async def test_get_device_state(client):
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/", payload={
            "valveState": "closed",
            "waterFlowing": False,
            "mqttConnected": True,
            "wifiConnected": True,
            "powerSupply": "external",
            "firmwareVersion": "2024.1.0",
            "waterMeter": {"volume": 567820, "duration": 3908},
            "uptime": 1024560,
            "serialNumber": "123123"
        })

        device_state = await client.async_get_device_state()
        assert device_state.valve_state == "closed"
        assert device_state.water_flow_indicator is False
        assert device_state.mqtt_status is True
        assert device_state.wifi_status is True
        assert device_state.power_supply == "external"
        assert device_state.firmware_version == "2024.1.0"
        assert device_state.uptime == 1024560
        assert device_state.water_meter.volume == 567820

@pytest.mark.asyncio
async def test_get_networking(client):
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/networking", payload={
            "mqttConnected": True,
            "wifiConnected": True,
            "ip": "192.168.21.37",
            "gateway": "192.168.1.0",
            "subnet": "192.168.1.0/24",
            "ssid": "MyWiFi",
            "rssi": -45,
            "wifiUpTime": 1024560,
            "mqttUpTime": 1023450
        })

        networking_data = await client.async_get_networking()
        assert networking_data.ip == "192.168.21.37"
        assert networking_data.gateway == "192.168.1.0"
        assert networking_data.subnet == "192.168.1.0/24"
        assert networking_data.ssid == "MyWiFi"
        assert networking_data.rssi == -45

@pytest.mark.asyncio
async def test_get_telemetry_data(client):
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/telemetry", payload={
            "flow": 6800,
            "pressure": 2320,
            "temperature": 23.5,
            "event": {"volume": 16000, "duration": 90},
            "errors": ["flow"]
        })

        telemetry_data = await client.async_get_telemetry_data()
        assert telemetry_data.flow == 6800
        assert telemetry_data.pressure == 2320
        assert telemetry_data.water_temperature == 23.5
        assert telemetry_data.ongoing_event.volume == 16000
        assert telemetry_data.ongoing_event.duration == 90
        assert "flow" in telemetry_data.errors

@pytest.mark.asyncio
async def test_patch_auto_shut_off(client):
    with aioresponses() as mock:
        mock.patch("http://testserver/api/sonic/auto-shut-off", status=204)

        result = await client.async_patch_auto_shut_off(enabled=True, duration=10, volume=5)
        assert result is True

@pytest.mark.asyncio
async def test_get_auto_shut_off(client):
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/auto-shut-off", payload={
            "volumeThreshold": 300,
            "durationThreshold": 60,
        })

        report = await client.async_get_auto_shut_off()
        assert report.volume_threshold == 300
        assert report.duration_threshold == 60

@pytest.mark.asyncio
async def test_get_auto_shut_off_report(client):
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/auto-shut-off/report", payload={
            "type": "VOLUME_THRESHOLD",
            "volume": 300,
            "duration": 60,
            "timestamp": 1623456789
        })

        report = await client.async_get_auto_shut_off_report()
        assert report.type == "VOLUME_THRESHOLD"
        assert report.volume == 300
        assert report.duration == 60
        assert report.timestamp == 1623456789

@pytest.mark.asyncio
async def test_set_webhook_url(client):
    with aioresponses() as mock:
        mock.put("http://testserver/api/sonic/webhook", status=204)

        result = await client.async_set_webhook_url("http://webhook.url")
        assert result is True

@pytest.mark.asyncio
async def test_set_valve(client):
    with aioresponses() as mock:
        mock.put("http://testserver/api/sonic/valve", status=204)

        result = await client.async_set_valve_state("open")
        assert result is True

@pytest.mark.asyncio
async def test_retry_logic(client):
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic", status=500)

        with pytest.raises(WatergateApiException):
            await client.async_get_device_state()

@pytest.mark.asyncio
async def test_custom_exception(client):
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic", exception=aiohttp.ClientError)

        with pytest.raises(WatergateApiException):
            await client.async_get_device_state()

@pytest.mark.asyncio
async def test_api_rate_limit_handling(client):
    # Simulate API rate limit response (HTTP 429)
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic", status=429)

        with pytest.raises(WatergateApiException):
            await client.async_get_device_state()

@pytest.mark.asyncio
async def test_get_networking_with_unexpected_status_code(client):
    # Simulate an unexpected status code (HTTP 403)
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/networking", status=403)

        with pytest.raises(WatergateApiException):
            await client.async_get_networking()

@pytest.mark.asyncio
async def test_set_webhook_url_invalid_response(client):
    # Simulate an invalid response when setting webhook (e.g., status 400)
    with aioresponses() as mock:
        mock.patch("http://testserver/api/sonic/webhook", status=400)

        with pytest.raises(WatergateApiException):
            await client.async_set_webhook_url("http://invalid-webhook.url")

@pytest.mark.asyncio
async def test_auto_shut_off_report_with_missing_fields(client):
    # Response with some fields missing
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/auto-shut-off/report", payload={"type": "VOLUME_THRESHOLD"})

        report = await client.async_get_auto_shut_off_report()
        assert report.type == "VOLUME_THRESHOLD"
        assert report.volume is None  # Missing fields should default to None

@pytest.mark.asyncio
async def test_get_device_state_v2(client):
    """Test fetching device state V2 with positive and negative water meters."""
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/", payload={
            "valveState": "open",
            "waterFlowing": True,
            "mqttConnected": True,
            "wifiConnected": True,
            "powerSupply": "external+battery",
            "firmwareVersion": "2024.2.1",
            "uptime": 5000,
            "serialNumber": "abc123",
            "waterMeter": {
                "positive": {"volume": 100000, "duration": 500},
                "negative": {"volume": 1000, "duration": 10}
            }
        })

        device_state = await client.async_get_device_state_v2()
        assert device_state.valve_state == "open"
        assert device_state.water_flow_indicator is True
        assert device_state.mqtt_status is True
        assert device_state.wifi_status is True
        assert device_state.power_supply == "external+battery"
        assert device_state.firmware_version == "2024.2.1"
        assert device_state.uptime == 5000
        assert device_state.serial_number == "abc123"
        assert device_state.water_meter_positive.volume == 100000
        assert device_state.water_meter_positive.duration == 500
        assert device_state.water_meter_negative.volume == 1000
        assert device_state.water_meter_negative.duration == 10

@pytest.mark.asyncio
async def test_get_device_state_v2_without_water_meter(client):
    """Test device state V2 with missing water meter data."""
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/", payload={
            "valveState": "closed",
            "waterFlowing": False,
            "mqttConnected": False,
            "wifiConnected": True,
            "powerSupply": "battery",
            "firmwareVersion": "2024.1.0",
            "uptime": 1234,
            "serialNumber": "xyz789"
        })

        device_state = await client.async_get_device_state_v2()
        assert device_state.valve_state == "closed"
        assert device_state.water_meter_positive is None
        assert device_state.water_meter_negative is None

@pytest.mark.asyncio
async def test_get_device_state_v2_network_error(client):
    """Test device state V2 network error handling."""
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/", status=500)

        with pytest.raises(WatergateApiException):
            await client.async_get_device_state_v2()

@pytest.mark.asyncio
async def test_injected_session():
    """Test that an injected aiohttp session is used and not closed by the client."""
    # Create a session to inject
    injected_session = aiohttp.ClientSession()
    
    try:
        # Create client with injected session
        client = WatergateLocalApiClient(base_url="http://testserver", session=injected_session)
        
        with aioresponses() as mock:
            mock.get("http://testserver/api/sonic/", payload={
                "valveState": "open",
                "waterFlowing": True,
                "mqttConnected": True,
                "wifiConnected": True,
                "powerSupply": "external",
                "firmwareVersion": "2024.1.0",
                "waterMeter": {"volume": 100, "duration": 10},
                "uptime": 1000,
                "serialNumber": "test123"
            })
            
            device_state = await client.async_get_device_state()
            assert device_state.valve_state == "open"
        
        # Close the client - should NOT close the injected session
        await client.async_close()
        
        # Verify the injected session is still open
        assert not injected_session.closed
    finally:
        # Clean up the injected session
        await injected_session.close()

@pytest.mark.asyncio
async def test_owned_session_is_closed():
    """Test that a client-owned session is properly closed."""
    client = WatergateLocalApiClient(base_url="http://testserver")
    
    with aioresponses() as mock:
        mock.get("http://testserver/api/sonic/", payload={
            "valveState": "closed",
            "waterFlowing": False,
            "mqttConnected": True,
            "wifiConnected": True,
            "powerSupply": "battery",
            "firmwareVersion": "2024.1.0",
            "waterMeter": {"volume": 200, "duration": 20},
            "uptime": 2000,
            "serialNumber": "test456"
        })
        
        device_state = await client.async_get_device_state()
        assert device_state.valve_state == "closed"
    
    # Get reference to the session before closing
    session = client._session
    assert session is not None
    assert not session.closed
    
    # Close the client - should close the owned session
    await client.async_close()
    
    # Verify the owned session is closed
    assert session.closed

@pytest.mark.asyncio
async def test_context_manager_with_injected_session():
    """Test context manager doesn't close injected session."""
    injected_session = aiohttp.ClientSession()
    
    try:
        async with WatergateLocalApiClient(base_url="http://testserver", session=injected_session) as client:
            with aioresponses() as mock:
                mock.get("http://testserver/api/sonic/", payload={
                    "valveState": "opening",
                    "waterFlowing": False,
                    "mqttConnected": True,
                    "wifiConnected": True,
                    "powerSupply": "external",
                    "firmwareVersion": "2024.1.0",
                    "waterMeter": {"volume": 300, "duration": 30},
                    "uptime": 3000,
                    "serialNumber": "test789"
                })
                
                device_state = await client.async_get_device_state()
                assert device_state.valve_state == "opening"
        
        # After exiting context, injected session should still be open
        assert not injected_session.closed
    finally:
        await injected_session.close()

@pytest.mark.asyncio
async def test_context_manager_with_owned_session():
    """Test context manager closes owned session."""
    session_ref = None
    
    async with WatergateLocalApiClient(base_url="http://testserver") as client:
        with aioresponses() as mock:
            mock.get("http://testserver/api/sonic/", payload={
                "valveState": "closing",
                "waterFlowing": True,
                "mqttConnected": True,
                "wifiConnected": True,
                "powerSupply": "battery",
                "firmwareVersion": "2024.1.0",
                "waterMeter": {"volume": 400, "duration": 40},
                "uptime": 4000,
                "serialNumber": "test000"
            })
            
            device_state = await client.async_get_device_state()
            assert device_state.valve_state == "closing"
            session_ref = client._session
    
    # After exiting context, owned session should be closed
    assert session_ref is not None
    assert session_ref.closed