import random
import types
from time import sleep
from unittest.mock import MagicMock

import pytest
import requests

from afancontrol.config import FanName, TempName
from afancontrol.fans import Fans
from afancontrol.metrics import PrometheusMetrics, prometheus_available
from afancontrol.pwmfan import PWMFanNorm
from afancontrol.report import Report
from afancontrol.temp import TempCelsius, TempStatus
from afancontrol.trigger import Triggers


@pytest.fixture
def requests_session():
    # Ignore system proxies, see https://stackoverflow.com/a/28521696
    with requests.Session() as session:
        session.trust_env = False
        yield session


@pytest.mark.skipif(
    not prometheus_available, reason="prometheus_client is not installed"
)
def test_prometheus_metrics(requests_session):
    mocked_fan = MagicMock(spec=PWMFanNorm)()
    mocked_triggers = MagicMock(spec=Triggers)()
    mocked_report = MagicMock(spec=Report)()

    port = random.randint(20000, 50000)
    metrics = PrometheusMetrics("127.0.0.1:%s" % port)
    with metrics:
        resp = requests_session.get("http://127.0.0.1:%s/metrics" % port)
        assert resp.status_code == 200
        assert "is_threshold 0.0" in resp.text

        with metrics.measure_tick():
            sleep(0.01)

        resp = requests_session.get("http://127.0.0.1:%s/metrics" % port)
        assert resp.status_code == 200
        assert "tick_duration_count 1.0" in resp.text
        assert "tick_duration_sum 0." in resp.text

        mocked_triggers.panic_trigger.is_alerting = True
        mocked_triggers.threshold_trigger.is_alerting = False

        mocked_fan.pwm_line_start = 100
        mocked_fan.pwm_line_end = 240
        mocked_fan.get_speed.return_value = 999
        mocked_fan.get_raw.return_value = 142
        mocked_fan.get = types.MethodType(PWMFanNorm.get, mocked_fan)
        mocked_fan.pwmfan.max_pwm = 255

        metrics.tick(
            temps={
                TempName("goodtemp"): TempStatus(
                    temp=TempCelsius(74.0),
                    min=TempCelsius(40.0),
                    max=TempCelsius(50.0),
                    panic=TempCelsius(60.0),
                    threshold=None,
                    is_panic=True,
                    is_threshold=False,
                ),
                TempName("failingtemp"): None,
            },
            fans=Fans(fans={FanName("test"): mocked_fan}, report=mocked_report),
            triggers=mocked_triggers,
        )

        resp = requests_session.get("http://127.0.0.1:%s/metrics" % port)
        assert resp.status_code == 200
        print(resp.text)
        assert 'temperature_current{temp_name="failingtemp"} NaN' in resp.text
        assert 'temperature_current{temp_name="goodtemp"} 74.0' in resp.text
        assert 'temperature_is_failing{temp_name="failingtemp"} 1.0' in resp.text
        assert 'temperature_is_failing{temp_name="goodtemp"} 0.0' in resp.text
        assert 'fan_rpm{fan_name="test"} 999.0' in resp.text
        assert 'fan_pwm{fan_name="test"} 142.0' in resp.text
        assert 'fan_pwm_normalized{fan_name="test"} 0.556' in resp.text
        assert 'fan_is_failing{fan_name="test"} 0.0' in resp.text
        assert "is_panic 1.0" in resp.text
        assert "is_threshold 0.0" in resp.text
        assert "last_metrics_tick_seconds_ago 0." in resp.text

    with pytest.raises(IOError):
        requests_session.get("http://127.0.0.1:%s/metrics" % port)


@pytest.mark.skipif(
    not prometheus_available, reason="prometheus_client is not installed"
)
def test_prometheus_faulty_fans_dont_break_metrics_collection(requests_session):
    mocked_fan = MagicMock(spec=PWMFanNorm)()
    mocked_triggers = MagicMock(spec=Triggers)()
    mocked_report = MagicMock(spec=Report)()

    port = random.randint(20000, 50000)
    metrics = PrometheusMetrics("127.0.0.1:%s" % port)
    with metrics:
        mocked_triggers.panic_trigger.is_alerting = False
        mocked_triggers.threshold_trigger.is_alerting = False

        mocked_fan.pwm_line_start = 100
        mocked_fan.pwm_line_end = 240
        mocked_fan.get_speed.side_effect = IOError
        mocked_fan.get_raw.side_effect = IOError

        # Must not raise despite the PWMFan methods raising above:
        metrics.tick(
            temps={TempName("failingtemp"): None},
            fans=Fans(fans={FanName("test"): mocked_fan}, report=mocked_report),
            triggers=mocked_triggers,
        )

        resp = requests_session.get("http://127.0.0.1:%s/metrics" % port)
        assert resp.status_code == 200
        assert 'fan_pwm_line_start{fan_name="test"} 100.0' in resp.text
        assert 'fan_pwm_line_end{fan_name="test"} 240.0' in resp.text
        assert 'fan_rpm{fan_name="test"} NaN' in resp.text
        assert 'fan_pwm{fan_name="test"} NaN' in resp.text
        assert 'fan_pwm_normalized{fan_name="test"} NaN' in resp.text
        assert 'fan_is_failing{fan_name="test"} 0.0' in resp.text
        assert "is_panic 0.0" in resp.text
        assert "is_threshold 0.0" in resp.text
