import pytest

from solo_tool.solo_tool import SoloTool
from player_mock import Player as MockPlayer

@pytest.fixture
def mockPlayer():
    return MockPlayer()

@pytest.fixture
def uut(mockPlayer):
    return SoloTool(mockPlayer)

@pytest.fixture
def testSongs():
    return [
        "test.flac",
        "test.mp3"
    ]

def test_keyPointAndSongSelection(uut, mockPlayer, testSongs):
    def checkJump(before, expectedAfter):
        mockPlayer.position = before
        uut.jump()
        assert mockPlayer.position == expectedAfter

    # Key point is initially unset
    assert uut.keyPoint is None

    # If no song is selected, setting the key point has no effect
    assert uut.song is None
    uut.keyPoint = 0.5
    assert uut.keyPoint is None

    # With a song selected, key point can be set and jumping works
    uut.addSong(testSongs[0])
    uut.keyPoints = [0.3, 0.5]

    uut.keyPoint = 0.6
    assert uut.keyPoint == 0.6
    checkJump(0.8, 0.6)

    # When another song is selected, the key point is set to 0.0
    uut.addSong(testSongs[1])
    uut.song = 1
    assert uut.keyPoint == 0.0
    checkJump(0.5, 0.0)

    # If the selected song has stored key points, the key point is set to the first one instead
    uut.song = 0
    assert uut.keyPoint == 0.3
    checkJump(0.5, 0.3)

def test_keyPointListAndSongSelection(uut, testSongs):
    # Key point list is initially unset, since no song is selected
    assert uut.keyPoint is None

    # If no song is selected, setting the key point list has no effect
    assert uut.song is None
    uut.keyPoints = [0.5]
    assert uut.keyPoints is None

    # When a song is added, key point list is initialized to empty
    uut.addSong(testSongs[0])
    assert uut.keyPoints == []
    
    # A new list can be assigned to the song, but it does not affect the current key point
    uut.keyPoints = [0.1, 0.3]
    assert uut.keyPoints == [0.1, 0.3]
    assert uut.keyPoint == 0.0

    # Each song has its own list of key points 
    uut.addSong(testSongs[1])
    uut.song = 1
    uut.keyPoints = [0.4]

    uut.song = 0
    assert uut.keyPoints == [0.1, 0.3]
    uut.song = 1
    assert uut.keyPoints == [0.4]

def test_keyPointEdgeCases(uut, testSongs):
    uut.addSong(testSongs[0])

    # Key point cannot be unset
    uut.keyPoint = None
    assert uut.keyPoint == 0.0

    # Valid key points are in [0, 1)
    uut.keyPoint = -0.1
    assert uut.keyPoint == 0.0

    uut.keyPoint = 1.0
    assert uut.keyPoint == 0.0

    uut.keyPoint = 0.999
    assert uut.keyPoint == 0.999

def test_keyPointListEdgeCases(uut, testSongs):
    uut.addSong(testSongs[0])
    
    # Key point list cannot be unset
    uut.keyPoints = None
    assert uut.keyPoints == []

    # Appending to the list has no effect
    uut.keyPoints.append(0.5)
    assert uut.keyPoints == []

    # Added key points are automatically de-duplicated, sanitized and sorted to ascending order
    uut.keyPoints = [0.2, 0.4, 0.1, 0.2, None, -0.5, 1.0, 1.5]
    assert uut.keyPoints == [0.1, 0.2, 0.4]

def test_keyPointSelectionNotification(uut, testSongs):
    called = False
    receivedValue = None
    def callback(value):
        nonlocal called, receivedValue
        called = True
        receivedValue = value

    uut.registerKeyPointSelectionCallback(callback)
    assert not called

    # Selecting a song for the first time sets the key point to 0.0
    uut.addSong(testSongs[0])
    assert called
    assert receivedValue == 0.0
    called = False

    # Changing the key point triggers a notification
    uut.keyPoint = 0.5
    assert called
    assert receivedValue == 0.5
    called = False

    # Adding list of key points does not trigger a notification
    uut.keyPoints = [0.2, 0.4]
    assert not called

    # Assigning the same key point again does not trigger a notification
    uut.keyPoint = 0.5
    assert not called

    # Changing song triggers the notification
    uut.addSong(testSongs[1])
    uut.song = 1
    assert called
    assert receivedValue == 0.0
    called = False

    # But only if the key point really changes
    uut.keyPoint = 0.2
    assert called
    assert receivedValue == 0.2
    called = False

    uut.song = 0
    assert not called

def test_keyPointListNotification(uut, testSongs):
    called = False
    receivedValue = None
    def callback(value):
        nonlocal called, receivedValue
        called = True
        receivedValue = value

    uut.registerKeyPointListCallback(callback)
    assert not called

    # Adding the first song triggers since the list is now not None
    uut.addSong(testSongs[0])
    assert called
    assert receivedValue == []
    called = False

    # Adding list of key points triggers
    uut.keyPoints = [0.2, 0.4]
    assert called
    assert receivedValue == [0.2, 0.4]
    called = False

    # Same list does not trigger
    uut.keyPoints = [0.2, 0.4]
    assert called
    assert receivedValue == [0.2, 0.4]
    called = False

    # Incrementing list of key points triggers after sanitization
    uut.keyPoints += [0.2, None, 0.1]
    assert called
    assert receivedValue == [0.1, 0.2, 0.4]
    called = False

    # Changing song triggers
    uut.addSong(testSongs[1])
    uut.song = 1
    assert called
    assert receivedValue == []
    called = False

    # But only if the list really changed
    uut.keyPoints = [0.1, 0.2, 0.4]
    assert called
    assert receivedValue == [0.1, 0.2, 0.4]
    called = False

    uut.song = 0
    assert not called