import pathlib
import shutil
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 prepared_tmp_path(tmp_path):
    testFiles = [
        "test.flac",
        "test.mp3",
        "test_session.json"
    ]
    for f in testFiles:
        shutil.copy(pathlib.Path(f), tmp_path)

    return tmp_path

def test_playerControls(uut, mockPlayer):
    assert mockPlayer.state == MockPlayer.STOPPED
    assert uut.playing == False
    uut.play()
    assert mockPlayer.state == MockPlayer.PLAYING
    assert uut.playing == True
    uut.pause()
    assert mockPlayer.state == MockPlayer.PAUSED
    assert uut.playing == False
    uut.stop()
    assert mockPlayer.state == MockPlayer.STOPPED
    assert uut.playing == False

    assert mockPlayer.rate == 1.0
    uut.rate = 0.5
    assert mockPlayer.rate == 0.5
    assert uut.rate == 0.5

    assert mockPlayer.position == 0.0
    uut.position = 0.5
    assert mockPlayer.position == 0.5
    assert uut.position == 0.5

    assert mockPlayer.volume == 1.0
    uut.volume = 0.5
    assert mockPlayer.volume == 0.5
    assert uut.volume == 0.5

def test_sanitizePlaybackRate(uut):
    # Initial value
    assert uut.rate == 1.0

    # Valid rates are >= 0.0, invalid is ignored
    uut.rate = -0.1
    assert uut.rate == 1.0

    uut.rate = 0.0
    assert uut.rate == 0.0
    
    uut.rate = 0.0001
    assert uut.rate == 0.0001

    uut.rate = 150.0
    assert uut.rate == 150.0

def test_sanitizePlaybackPosition(uut):
    # Initial value
    assert uut.position == 0.0

    # Valid positions are in [0, 1], invalid is limited
    uut.position = 0.2
    assert uut.position == 0.2

    uut.position = -0.1
    assert uut.position == 0.0

    uut.position = 1.0
    assert uut.position == 1.0

    uut.position = 0.4
    assert uut.position == 0.4

    uut.position = 1.5
    assert uut.position == 1.0

def test_sanitizePlaybackVolume(uut):
    # Initial value
    assert uut.volume == 1.0

    # Valid volumes are >= 0.0, invalid is ignored
    uut.volume = -0.1
    assert uut.volume == 1.0

    uut.volume = 0.0
    assert uut.volume == 0.0

    uut.volume = 1.0
    assert uut.volume == 1.0

    uut.volume = 150.0
    assert uut.volume == 150.0

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

    # Key points are None as long as no song is selected
    uut.keyPoints = [0.1, 0.2]
    uut.keyPoint = 0.5
    assert uut.keyPoints is None
    assert uut.keyPoint is None

    uut.addSong("test.flac")
    uut.addSong("test.mp3")

    # Once a song is selected, jump to start by default
    assert uut.keyPoint == 0.0
    checkJump(0.5, 0.0)

    # By default songs have an empty list of key points
    assert uut.keyPoints == []

    uut.keyPoints = [0.2, 0.4, 0.1, 0.2]

    # Added key points are not automatically selected
    assert uut.keyPoint == 0.0
    checkJump(0.1, 0.0)

    # Any key point can be selected
    uut.keyPoint = uut.keyPoints[0]
    checkJump(0.0, uut.keyPoints[0])

    uut.keyPoint = 0.5
    checkJump(0.0, 0.5)

def test_sanitizeKeyPoint(uut):
    song = "test.flac"
    uut.addSong(song)
    uut.song = 0
    uut.keyPoints = [0.2, 0.4, 0.1, 0.2, None, -0.5, 1.0, 1.5]

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

    # Key point and key point list cannot be none
    uut.keyPoint = 0.5

    uut.keyPoint = None
    assert uut.keyPoint == 0.5

    uut.keyPoints = None
    assert uut.keyPoints == [0.1, 0.2, 0.4]

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

    uut.keyPoint = 1.0
    assert uut.keyPoint == 0.5

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

def test_keyPointsPerSong(uut, mockPlayer):
    songs = [
        ("test.flac", [0.0, 0.5]),
        ("test.mp3", [0.1])
    ]
    
    # Key points list is set for the selected song
    for i, (song, keyPoints) in enumerate(songs):
        uut.addSong(song)
        uut.song = i
        uut.keyPoints = keyPoints

    # Key points list is automatically loaded when the song selection changes
    # Active key point is always reset to 0 when song selection changes
    for i, (song, keyPoints) in enumerate(songs):
        uut.keyPoint = 0.5
        uut.song = i
        assert uut.keyPoints == keyPoints
        assert uut.keyPoint == 0.0

    # Key points are copied, not stored by reference
    for i, (song, keyPoints) in enumerate(songs):
        uut.song = i
        keyPoints.append(1.0)
        assert 1.0 not in uut.keyPoints

def test_playingStateNotification(uut, mockPlayer):
    song = "test.flac"
    uut.addSong(song)
    uut.song = 0

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

    uut.registerPlayingStateCallback(callback)

    assert mockPlayer.state == MockPlayer.STOPPED
    assert not called

    uut.play()
    assert called
    assert receivedValue == True
    called = False
    uut.play()
    assert not called

    uut.pause()
    assert called
    assert receivedValue == False
    called = False
    uut.pause()
    assert not called

    uut.play()
    assert called
    assert receivedValue == True
    called = False

    uut.stop()
    assert called
    assert receivedValue == False
    called = False
    uut.stop()
    assert not called

def test_playbackVolumeNotification(uut, mockPlayer):
    song = "test.flac"
    uut.addSong(song)
    uut.song = 0

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

    uut.registerVolumeCallback(callback)

    assert not called

    uut.volume = 0.3
    assert called
    assert receivedValue == 0.3
    called = False

    uut.volume = 0.3
    assert not called

def test_playbackRateNotification(uut, mockPlayer):
    song = "test.flac"
    uut.addSong(song)
    uut.song = 0

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

    uut.registerRateCallback(callback)

    assert not called

    uut.rate = 0.5
    assert called
    assert receivedValue == 0.5
    called = False

    uut.rate = 0.5
    assert not called


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

    uut.registerCurrentKeyPointCallback(callback)
    assert not called

    song = "test.flac"
    uut.addSong(song)
    uut.song = 0

    # Selecting a song for the first time sets the key point to 0.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

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

    uut.registerKeyPointsCallback(callback)
    assert not called

    song = "test.flac"
    uut.addSong(song)
    uut.song = 0
    assert not called

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

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

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