from django.utils import timezone as tz

import pytest
from model_bakery import baker

from aircox import models
from aircox.test import interface
from aircox_streamer import controllers


@pytest.fixture
def monitor(streamer):
    streamer.calls = {}
    return controllers.Monitor(
        streamer,
        tz.timedelta(seconds=10),
        cancel_timeout=tz.timedelta(minutes=10),
        sync_timeout=tz.timedelta(minutes=5),
    )


@pytest.fixture
def diffusion(program, episode, sound):
    return baker.make(
        models.Diffusion,
        program=program,
        episode=episode,
        start=tz.now() - tz.timedelta(minutes=2),
        end=tz.now() + tz.timedelta(minutes=4),
        schedule=None,
        type=models.Diffusion.TYPE_ON_AIR,
    )


@pytest.fixture
def source(monitor, streamer, sound, diffusion):
    source = next(monitor.streamer.playlists)
    source.uri = sound.file.path
    source.episode_id = sound.episode_id
    source.air_time = diffusion.start + tz.timedelta(seconds=10)
    return source


@pytest.fixture
def tracks(sound):
    items = [baker.prepare(models.Track, sound=sound, position=i, timestamp=i * 60) for i in range(0, 4)]
    models.Track.objects.bulk_create(items)
    return items


@pytest.fixture
def log(station, source, sound):
    return baker.make(
        models.Log,
        station=station,
        type=models.Log.TYPE_START,
        sound=sound,
        source=source.id,
    )


class TestMonitor:
    @pytest.mark.django_db(transaction=True)
    def test_last_diff_start(self, monitor, diffusion, log):
        log.diffusion = diffusion
        log.save()
        assert monitor.last_diff_start == log

    @pytest.mark.django_db(transaction=True)
    def test___init__(self, monitor):
        assert isinstance(monitor.logs, models.LogQuerySet)
        assert isinstance(monitor.last_sound_logs, dict)

    @pytest.mark.django_db(transaction=True)
    def test_get_logs_queryset(self, monitor, station, sounds):
        query = monitor.get_logs_queryset()
        assert all(log.station_id == station.pk for log in query)

    @pytest.mark.django_db(transaction=True)
    def test_init_last_sound_logs(self, monitor, source, log):
        monitor.init_last_sound_logs()
        assert monitor.last_sound_logs[source.id] == log

    @pytest.mark.django_db(transaction=True)
    def test_monitor(self, monitor, source, log, sound):
        monitor.streamer.is_ready = True
        monitor.streamer.source = source
        interface(
            monitor,
            {
                "trace_sound": log,
                "trace_tracks": None,
                "handle_diffusions": None,
                "sync": None,
            },
        )

        monitor.monitor()
        assert monitor.streamer.calls.get("fetch")
        assert monitor.calls["trace_sound"] == ((source,), {})
        assert monitor.calls["trace_tracks"] == ((log,), {})
        assert monitor.calls["handle_diffusions"]
        assert monitor.calls["sync"]

    @pytest.mark.django_db(transaction=True)
    def test_monitor_streamer_not_ready(self, monitor):
        monitor.streamer.is_ready = False
        interface(
            monitor,
            {
                "trace_sound": log,
                "trace_tracks": None,
                "handle_diffusions": None,
                "sync": None,
            },
        )

        monitor.monitor()
        assert not monitor.streamer.calls.get("fetch")
        assert monitor.calls["trace_sound"] is None
        assert monitor.calls["trace_tracks"] is None
        assert not monitor.calls["handle_diffusions"]
        assert not monitor.calls["sync"]

    @pytest.mark.django_db(transaction=True)
    def test_monitor_no_source_uri(self, monitor, log):
        source.uri = None
        monitor.streamer.is_ready = True
        monitor.streamer.source = source
        interface(
            monitor,
            {
                "trace_sound": log,
                "trace_tracks": None,
                "handle_diffusions": None,
                "sync": None,
            },
        )

        monitor.monitor()
        assert monitor.streamer.calls.get("fetch")
        assert monitor.calls["trace_sound"] is None
        assert monitor.calls["trace_tracks"] is None
        assert monitor.calls["handle_diffusions"]
        assert monitor.calls["sync"]

    @pytest.mark.django_db(transaction=True)
    def test_trace_sound(self, monitor, diffusion, source, sound):
        monitor.last_sound_logs[source.id] = None

        result = monitor.trace_sound(source)
        assert result.type == models.Log.TYPE_ON_AIR
        assert result.source == source.id
        assert result.sound == sound
        assert result.diffusion == diffusion

    @pytest.mark.django_db(transaction=True)
    def test_trace_sound_returns_last_log(self, monitor, source, sound, log):
        log.sound = sound
        monitor.last_sound_logs[source.id] = log

        result = monitor.trace_sound(source)
        assert result == log

    @pytest.mark.django_db(transaction=True)
    def test_trace_tracks(self, monitor, log, tracks):
        interface(monitor, {"log": None})
        for track in tracks:
            log.date = tz.now() - tz.timedelta(seconds=track.timestamp + 5)
            monitor.trace_tracks(log)

        assert monitor.calls["log"]
        log_by_track = [call[1].get("track") for call in monitor.calls["log"]]
        # only one call of log
        assert all(log_by_track.count(track) for track in tracks)

    @pytest.mark.django_db(transaction=True)
    def test_trace_tracks_returns_on_log_diffusion(self, monitor, log, diffusion, tracks):
        log.diffusion = None
        monitor.trace_tracks(log)

    @pytest.mark.django_db(transaction=True)
    def test_trace_tracks_returns_on_no_tracks_exists(self, monitor, log):
        log.diffusion = None
        monitor.trace_tracks(log)

    @pytest.mark.django_db(transaction=True)
    def test_handle_diffusions(self, monitor, streamer, diffusion, sound):
        interface(
            monitor,
            {
                "start_diff": None,
                "cancel_diff": None,
            },
        )
        streamer.dealer.queue = [1]
        streamer.dealer.rid = None
        streamer.dealer.remaining = monitor.delay.total_seconds() - 10

        monitor.handle_diffusions()
        assert monitor.calls["start_diff"] == (
            (streamer.dealer, diffusion),
            {},
        )
        assert not monitor.calls["cancel_diff"]

    @pytest.mark.django_db(transaction=True)
    def test_handle_diffusions_returns_on_diff(self, monitor, streamer, diffusion, log):
        interface(
            monitor,
            {
                "start_diff": None,
                "cancel_diff": None,
            },
        )
        streamer.dealer.queue = [1]
        streamer.dealer.rid = None
        streamer.dealer.remaining = monitor.delay.total_seconds() - 10
        diffusion.start = tz.now() - tz.timedelta(minutes=30)
        diffusion.end = tz.now() - tz.timedelta(minutes=20)
        diffusion.save()

        monitor.handle_diffusions()
        assert not monitor.calls["start_diff"]
        assert not monitor.calls["cancel_diff"]

    @pytest.mark.django_db(transaction=True)
    def test_handle_diffusions_returns_on_diff_log_exists(self, monitor, streamer, diffusion, log):
        interface(
            monitor,
            {
                "start_diff": None,
                "cancel_diff": None,
            },
        )
        streamer.dealer.queue = [1]
        streamer.dealer.rid = None
        streamer.dealer.remaining = monitor.delay.total_seconds() - 10
        log.diffusion = diffusion
        log.save()

        monitor.handle_diffusions()
        assert not monitor.calls["start_diff"]
        assert not monitor.calls["cancel_diff"]

    @pytest.mark.django_db(transaction=True)
    def test_handle_diffusions_cancel_diff(self, monitor, streamer, diffusion):
        interface(
            monitor,
            {
                "start_diff": None,
                "cancel_diff": None,
            },
        )
        streamer.dealer.queue = None
        streamer.dealer.rid = "13"
        streamer.dealer.remaining = monitor.delay.total_seconds() + 10
        diffusion.start = tz.now() - monitor.cancel_timeout - tz.timedelta(seconds=30)
        diffusion.end = tz.now() + tz.timedelta(minutes=30)
        diffusion.save()

        monitor.handle_diffusions()
        assert not monitor.calls["start_diff"]
        assert monitor.calls["cancel_diff"] == (
            (streamer.dealer, diffusion),
            {},
        )

    @pytest.mark.django_db(transaction=True)
    def test_log(self, monitor, source):
        log = monitor.log("source", type=models.Log.TYPE_START, comment="test")
        assert log.source == "source"
        assert log.type == models.Log.TYPE_START
        assert log.comment == "test"

    @pytest.mark.django_db(transaction=True)
    def test_start_diff(self, monitor, diffusion, source, episode, sound, tracks):
        result = {}
        monitor.log = lambda **kw: result.update(kw)

        monitor.start_diff(source, diffusion)
        assert source.calls["push"] == (sound.file.path,)
        assert result == {
            "type": models.Log.TYPE_START,
            "source": source.id,
            "diffusion": diffusion,
            "comment": str(diffusion),
        }

    @pytest.mark.django_db(transaction=True)
    def test_cancel_diff(self, monitor, source, diffusion):
        result = {}
        monitor.log = lambda **kw: result.update(kw)

        monitor.cancel_diff(source, diffusion)
        assert diffusion.type == models.Log.TYPE_CANCEL
        assert result == {
            "type": models.Log.TYPE_CANCEL,
            "source": source.id,
            "diffusion": diffusion,
            "comment": str(diffusion),
        }

    @pytest.mark.django_db(transaction=True)
    def test_sync(self, monitor):
        now = tz.now()
        monitor.sync_next = now - tz.timedelta(minutes=1)
        monitor.sync()

        assert monitor.sync_next >= now + monitor.sync_timeout
        assert all(source.calls.get("sync") for source in monitor.streamer.playlists)

    @pytest.mark.django_db(transaction=True)
    def test_sync_timeout_not_reached_skip_sync(self, monitor):
        monitor.sync_next = tz.now() + tz.timedelta(seconds=monitor.sync_timeout.total_seconds() + 20)
        monitor.sync()
        assert all(not source.calls.get("sync") for source in monitor.streamer.playlists)
