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 checkLimit(uut, mockPlayer, aLimit, bLimit):
    mockPlayer.position = bLimit - 0.1
    uut.tick()
    assert mockPlayer.position == bLimit - 0.1

    mockPlayer.position = bLimit + 0.1
    uut.tick()
    assert mockPlayer.position == aLimit

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

    assert mockPlayer.rate == 1.0
    uut.setPlaybackRate(0.5)
    assert mockPlayer.rate == 0.5

    assert mockPlayer.position == 0.0
    uut.setPlaybackPosition(0.5)
    assert mockPlayer.position == 0.5

    assert mockPlayer.volume == 1.0
    uut.setPlaybackVolume(0.5)
    assert mockPlayer.volume == 0.5

def test_addAndSetSongs(uut, mockPlayer):
    songs = [
        "test.flac",
        "test.mp3"
    ]

    for s in songs:
        uut.addSong(s)
    assert mockPlayer.currentSong == None
    
    for i, s in enumerate(songs):
        uut.song = i
        assert mockPlayer.currentSong == songs[i]
        assert uut.song == i

def test_addAndSetAbLimits(uut, mockPlayer):
    song = "test.flac"
    abLimits = [
        [0.2, 0.4],
        [0.1, 0.3]
    ]

    uut.addSong(song)
    uut.song = 0

    for ab in abLimits:
        uut.storeAbLimits(ab[0], ab[1])

    mockPlayer.position = 0.0
    uut.tick()
    assert mockPlayer.position == 0.0

    mockPlayer.position = 0.5
    uut.tick()
    assert mockPlayer.position == 0.5

    uut.loadAbLimits(0)

    uut.tick()
    assert mockPlayer.position == 0.5

    uut.setAbLimitEnable(True)

    uut.tick()
    assert mockPlayer.position == 0.2

    uut.tick()
    assert mockPlayer.position == 0.2

    uut.loadAbLimits(1)
    uut.tick()
    assert mockPlayer.position == 0.2

    mockPlayer.position = 0.8
    uut.tick()
    assert mockPlayer.position == 0.1

def test_abLimitEnabledGetter(uut):
    assert not uut.isAbLimitEnabled()

    uut.setAbLimitEnable(True)
    assert uut.isAbLimitEnabled()

    uut.setAbLimitEnable(False)
    assert not uut.isAbLimitEnabled()

def test_multipleSongsAndAbLimits(uut, mockPlayer):
    songs = [
        "test.flac",
        "test.mp3"
    ]
    abLimits = [
        [0.2, 0.4],
        [0.5, 0.7]
    ]
    
    for s in songs:
        uut.addSong(s)

    for i, l in enumerate(abLimits):
        uut.song = i
        uut.storeAbLimits(l[0], l[1])

    uut.setAbLimitEnable(True)
   
    for i, l in enumerate(abLimits):
        uut.song = i
        uut.loadAbLimits(0)

        mockPlayer.position = l[0]
        uut.tick()
        assert mockPlayer.position == l[0]

        mockPlayer.position = l[1] + 0.1
        uut.tick()
        assert mockPlayer.position == l[0]

def test_storeAbLimitsWithoutSong(uut, mockPlayer):
    song = "test.flac"
    abLimit = [0.2, 0.4]
    overflow = abLimit[1] + 0.1
    default = 0.0
    mockPlayer.position = overflow
    uut.setAbLimitEnable(True)

    uut.storeAbLimits(abLimit[0], abLimit[1])
    uut.tick()
    assert mockPlayer.position == default
    mockPlayer.position = overflow
    
    uut.loadAbLimits(0)
    uut.tick()
    assert mockPlayer.position == default
    mockPlayer.position = overflow

    uut.addSong(song)
    uut.tick()
    assert mockPlayer.position == default
    mockPlayer.position = overflow

    uut.loadAbLimits(0)
    uut.tick()
    assert mockPlayer.position == default
    mockPlayer.position = overflow

    uut.song = 0
    uut.tick()
    assert mockPlayer.position == default
    mockPlayer.position = overflow

    uut.loadAbLimits(0)
    uut.tick()
    assert mockPlayer.position == default
    mockPlayer.position = overflow

    uut.storeAbLimits(abLimit[0], abLimit[1])
    uut.tick()
    assert mockPlayer.position == default
    mockPlayer.position = overflow

    uut.loadAbLimits(0)
    uut.tick()
    assert mockPlayer.position == abLimit[0]

def test_nextAndPreviousAbLimit(uut, mockPlayer):
    song = "test.flac"
    abLimits = [
        [0.2, 0.4],
        [0.1, 0.3]
    ]

    uut.addSong(song)
    uut.song = 0
    uut.setAbLimitEnable(True)

    for ab in abLimits:
        uut.storeAbLimits(ab[0], ab[1])

    checkLimit(uut, mockPlayer, 0.0, 0.0) # default limits 

    uut.nextStoredAbLimits()
    checkLimit(uut, mockPlayer, abLimits[0][0], abLimits[0][1])

    uut.nextStoredAbLimits()
    checkLimit(uut, mockPlayer, abLimits[1][0], abLimits[1][1])

    uut.nextStoredAbLimits()
    checkLimit(uut, mockPlayer, abLimits[1][0], abLimits[1][1])

    uut.previousStoredAbLimits()
    checkLimit(uut, mockPlayer, abLimits[0][0], abLimits[0][1])

    uut.previousStoredAbLimits()
    checkLimit(uut, mockPlayer, abLimits[0][0], abLimits[0][1])

def test_abLimitsWhenChangingSongs(uut, mockPlayer):
    songs = [
        "test.flac",
        "test.mp3"
    ]
    abLimits = [
        [0.2, 0.4],
        [0.1, 0.3],
        [0.7, 0.8]
    ]
    uut.setAbLimitEnable(True)

    for s in songs:
        uut.addSong(s)

    uut.song = 0
    for ab in abLimits:
        uut.storeAbLimits(ab[0], ab[1])

    uut.song = 1
    uut.storeAbLimits(abLimits[0][0], abLimits[0][1])

    uut.song = 0
    uut.loadAbLimits(len(abLimits) - 1)
    checkLimit(uut, mockPlayer, abLimits[-1][0], abLimits[-1][1])

    uut.song = 1
    checkLimit(uut, mockPlayer, abLimits[-1][0], abLimits[-1][1])

    uut.previousStoredAbLimits()
    checkLimit(uut, mockPlayer, abLimits[0][0], abLimits[0][1])
    
def test_loadAndSaveSession(prepared_tmp_path):
    mockPlayer = MockPlayer()
    uut = SoloTool(mockPlayer)

    loadedSessionFile = prepared_tmp_path / "test_session.json"
    savedSessionFile = prepared_tmp_path / "test_session_save.json"

    uut.loadSession(loadedSessionFile)
    uut.saveSession(savedSessionFile)

    import json
    with open(loadedSessionFile, "r") as f:
        loadedSession = json.loads(f.read())
        
    with open(savedSessionFile, "r") as f:
        savedSession = json.loads(f.read())

    assert loadedSession == savedSession

def test_addInexistentFile(uut, mockPlayer):
    song = "not/a/real/file"

    uut.addSong(song)
    uut.song = 0

    assert mockPlayer.currentSong == None

def test_getters(uut, mockPlayer):
    song = "test.flac"
    abLimit = [0.2, 0.4]

    uut.addSong(song)
    uut.song = 0
    uut.storeAbLimits(abLimit[0], abLimit[1])

    assert uut.songList == [song]

    limits = uut.getStoredAbLimits()
    assert len(limits) == 1
    assert limits[0][0] == abLimit[0]
    assert limits[0][1] == abLimit[1]

    mockPlayer.position = 0.8
    assert uut.getPlaybackPosition() == 0.8

    mockPlayer.volume = 0.8
    assert uut.getPlaybackVolume() == 0.8

    mockPlayer.rate = 0.5
    assert uut.getPlaybackRate() == 0.5

def test_setTemporaryLimits(uut, mockPlayer):
    song = "test.flac"
    abLimits = [
        [0.2, 0.4],
        [0.1, 0.4]
    ]
    overflow = 0.5

    uut.setAbLimitEnable(True)
    mockPlayer.position = overflow
    uut.addSong(song)
    uut.song = 0
    uut.storeAbLimits(abLimits[0][0], abLimits[0][1])
    uut.loadAbLimits(0)

    uut.setAbLimits(abLimits[1][0], abLimits[1][1])
    uut.tick()
    assert mockPlayer.position == abLimits[1][0]

def test_jumpToA(uut, mockPlayer):
    abLimits = (0.2, 0.4)
    initialPosition = 0.8

    mockPlayer.position = initialPosition
    
    uut.jumpToA()
    assert mockPlayer.position == 0.0 # default AB controller A limit

    uut.setAbLimits(abLimits[0], abLimits[1])
    uut.jumpToA()
    assert mockPlayer.position == abLimits[0]

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.registerPlaybackVolumeCallback(callback)

    assert not called

    uut.setPlaybackVolume(0.3)
    assert called
    assert receivedValue == 0.3
    called = False

    uut.setPlaybackVolume(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.registerPlaybackRateCallback(callback)

    assert not called

    uut.setPlaybackRate(0.5)
    assert called
    assert receivedValue == 0.5
    called = False

    uut.setPlaybackRate(0.5)
    assert not called

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

    uut.registerCurrentSongCallback(callback)
    assert not called

    songs = [
        "test.flac",
        "test.mp3"
    ]
    uut.addSong(songs[0])
    assert not called

    uut.song = 0
    assert called
    assert receivedValue == 0
    called = False

    uut.addSong(songs[1])
    assert not called

    uut.song = 0
    assert not called

    uut.song = 1
    assert called
    assert receivedValue == 1
    called = False

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

    uut.registerCurrentAbLimitsCallback(callback)
    assert not called

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

    abLimits = [
        (0.2, 0.3),
        (0.4, 0.5)
    ]
    uut.storeAbLimits(abLimits[0][0], abLimits[0][1])
    assert not called
    uut.storeAbLimits(abLimits[1][0], abLimits[1][1])
    assert not called

    uut.loadAbLimits(0)
    assert called
    assert receivedValue == 0
    called = False

    uut.loadAbLimits(0)
    assert not called

    uut.loadAbLimits(1)
    assert called
    assert receivedValue == 1
    called = False

    uut.previousStoredAbLimits()
    assert called
    assert receivedValue == 0
    called = False

    uut.previousStoredAbLimits()
    assert not called
    
    uut.nextStoredAbLimits()
    assert called
    assert receivedValue == 1
    called = False

    uut.nextStoredAbLimits()
    assert not called

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

    uut.registerAbLimitEnabledCallback(callback)
    assert not called

    uut.setAbLimitEnable(False)
    assert not called
    assert receivedValue is None

    uut.setAbLimitEnable(True)
    assert called
    assert receivedValue == True
    called = False
    receivedValue = None

    uut.setAbLimitEnable(True)
    assert not called
    assert receivedValue is None

    uut.setAbLimitEnable(False)
    assert called
    assert receivedValue == False