# Standard library imports
import unittest

# PyQt imports
from PyQt6.QtCore import Qt, QModelIndex

# Third party imports

# App imports
from app.helpers import get_all_track_metadata
from app import playlistmodel
from app.models import (
    db,
    Playlists,
    Tracks,
)


class TestMMMiscTracks(unittest.TestCase):
    def setUp(self):
        PLAYLIST_NAME = "tracks playlist"
        self.test_tracks = [
            "testdata/isa.mp3",
            "testdata/isa_with_gap.mp3",
            "testdata/loser.mp3",
            "testdata/lovecats-10seconds.mp3",
            "testdata/lovecats.mp3",
            "testdata/mom.mp3",
            "testdata/sitting.mp3",
            "testdata/wrb.flac",
        ]

        db.create_all()

        # Create a playlist and model
        with db.Session() as session:
            self.playlist = Playlists(session, PLAYLIST_NAME, template_id=0)
            self.model = playlistmodel.PlaylistModel(self.playlist.id, is_template=False)

            for row in range(len(self.test_tracks)):
                track_path = self.test_tracks[row % len(self.test_tracks)]
                track = Tracks(session, **get_all_track_metadata(track_path))
                self.model.insert_row(
                    proposed_row_number=row, track_id=track.id, note=f"{row=}"
                )

        session.commit()

    def tearDown(self):
        db.drop_all()

    def test_8_row_playlist(self):
        # Test auto-created playlist

        assert self.model.rowCount() == 8
        assert max(self.model.playlist_rows.keys()) == 7
        for row in range(self.model.rowCount()):
            assert row in self.model.playlist_rows
            assert self.model.playlist_rows[row].row_number == row

    def test_timing_one_track(self):
        START_ROW = 0
        END_ROW = 2

        self.model.insert_row(proposed_row_number=START_ROW, note="start+")
        self.model.insert_row(proposed_row_number=END_ROW, note="-")

        prd = self.model.playlist_rows[START_ROW]
        qv_value = self.model._display_role(
            START_ROW, playlistmodel.HEADER_NOTES_COLUMN, prd
        )
        assert qv_value == "start [1 tracks, 4:23 unplayed]"


class TestMMMiscNoPlaylist(unittest.TestCase):
    PLAYLIST_NAME = "tracks playlist"
    test_tracks = [
        "testdata/isa.mp3",
        "testdata/isa_with_gap.mp3",
        "testdata/loser.mp3",
        "testdata/lovecats-10seconds.mp3",
        "testdata/lovecats.mp3",
        "testdata/mom.mp3",
        "testdata/sitting.mp3",
    ]

    def setUp(self):
        db.create_all()

    def tearDown(self):
        db.drop_all()

    def test_insert_track_new_playlist(self):
        # insert a track into a new playlist
        with db.Session() as session:
            playlist = Playlists(session, self.PLAYLIST_NAME, template_id=0)
            # Create a model
            model = playlistmodel.PlaylistModel(playlist.id, is_template=False)
            # test repr
            _ = str(model)

            track_path = self.test_tracks[0]
            metadata = get_all_track_metadata(track_path)
            track = Tracks(session, **metadata)
            model.insert_row(proposed_row_number=0, track_id=track.id)

            prd = model.playlist_rows[model.rowCount() - 1]
            # test repr
            _ = str(prd)

            assert (
                model._edit_role(
                    model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd
                )
                == metadata["title"]
            )


class TestMMMiscRowMove(unittest.TestCase):
    PLAYLIST_NAME = "rowmove playlist"
    ROWS_TO_CREATE = 11

    def setUp(self):
        db.create_all()

        with db.Session() as session:
            self.playlist = Playlists(session, self.PLAYLIST_NAME, template_id=0)
            self.model = playlistmodel.PlaylistModel(self.playlist.id, is_template=False)
            for row in range(self.ROWS_TO_CREATE):
                self.model.insert_row(proposed_row_number=row, note=str(row))

        session.commit()

    def tearDown(self):
        db.drop_all()

    def test_move_rows_test2(self):
        # move row 3 to row 5
        self.model.move_rows([3], 5)
        # Check we have all rows and plr_rownums are correct
        for row in range(self.model.rowCount()):
            assert row in self.model.playlist_rows
            assert self.model.playlist_rows[row].row_number == row
            if row not in [3, 4, 5]:
                assert self.model.playlist_rows[row].note == str(row)
            elif row == 3:
                assert self.model.playlist_rows[row].note == str(4)
            elif row == 4:
                assert self.model.playlist_rows[row].note == str(3)
            elif row == 5:
                assert self.model.playlist_rows[row].note == str(5)

    def test_move_rows_test3(self):
        # move row 4 to row 3

        self.model.move_rows([4], 3)

        # Check we have all rows and plr_rownums are correct
        for row in range(self.model.rowCount()):
            assert row in self.model.playlist_rows
            assert self.model.playlist_rows[row].row_number == row
            if row not in [3, 4]:
                assert self.model.playlist_rows[row].note == str(row)
            elif row == 3:
                assert self.model.playlist_rows[row].note == str(4)
            elif row == 4:
                assert self.model.playlist_rows[row].note == str(3)

    def test_move_rows_test4(self):
        # move row 4 to row 2

        self.model.move_rows([4], 2)

        # Check we have all rows and plr_rownums are correct
        for row in range(self.model.rowCount()):
            assert row in self.model.playlist_rows
            assert self.model.playlist_rows[row].row_number == row
            if row not in [2, 3, 4]:
                assert self.model.playlist_rows[row].note == str(row)
            elif row == 2:
                assert self.model.playlist_rows[row].note == str(4)
            elif row == 3:
                assert self.model.playlist_rows[row].note == str(2)
            elif row == 4:
                assert self.model.playlist_rows[row].note == str(3)

    def test_move_rows_test5(self):
        # move rows [1, 4, 5, 10] → 8

        self.model.move_rows([1, 4, 5, 10], 8)

        # Check we have all rows and plr_rownums are correct
        new_order = []
        for row in range(self.model.rowCount()):
            assert row in self.model.playlist_rows
            assert self.model.playlist_rows[row].row_number == row
            new_order.append(int(self.model.playlist_rows[row].note))
        assert new_order == [0, 2, 3, 6, 7, 1, 4, 5, 10, 8, 9]

    def test_move_rows_test6(self):
        # move rows [3, 6] → 5

        self.model.move_rows([3, 6], 5)

        # Check we have all rows and plr_rownums are correct
        new_order = []
        for row in range(self.model.rowCount()):
            assert row in self.model.playlist_rows
            assert self.model.playlist_rows[row].row_number == row
            new_order.append(int(self.model.playlist_rows[row].note))
        assert new_order == [0, 1, 2, 4, 3, 6, 5, 7, 8, 9, 10]

    def test_move_rows_test7(self):
        # move rows [3, 5, 6] → 8

        self.model.move_rows([3, 5, 6], 8)

        # Check we have all rows and plr_rownums are correct
        new_order = []
        for row in range(self.model.rowCount()):
            assert row in self.model.playlist_rows
            assert self.model.playlist_rows[row].row_number == row
            new_order.append(int(self.model.playlist_rows[row].note))
        assert new_order == [0, 1, 2, 4, 7, 3, 5, 6, 8, 9, 10]

    def test_move_rows_test8(self):
        # move rows [7, 8, 10] → 5

        self.model.move_rows([7, 8, 10], 5)

        # Check we have all rows and plr_rownums are correct
        new_order = []
        for row in range(self.model.rowCount()):
            assert row in self.model.playlist_rows
            assert self.model.playlist_rows[row].row_number == row
            new_order.append(int(self.model.playlist_rows[row].note))
        assert new_order == [0, 1, 2, 3, 4, 7, 8, 10, 5, 6, 9]

    def test_move_rows_test9(self):
        # move rows [1, 2, 3] → 0
        # Replicate issue 244

        self.model.move_rows([0, 1, 2, 3], 0)

        # Check we have all rows and plr_rownums are correct
        new_order = []
        for row in range(self.model.rowCount()):
            assert row in self.model.playlist_rows
            assert self.model.playlist_rows[row].row_number == row
            new_order.append(int(self.model.playlist_rows[row].note))
        assert new_order == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    def test_insert_header_row_end(self):
        # insert header row at end of playlist

        note_text = "test text"

        assert self.model.rowCount() == self.ROWS_TO_CREATE
        self.model.insert_row(proposed_row_number=None, note=note_text)
        assert self.model.rowCount() == self.ROWS_TO_CREATE + 1
        prd = self.model.playlist_rows[self.model.rowCount() - 1]
        # Test against edit_role because display_role for headers is
        # handled differently (sets up row span)
        assert (
            self.model._edit_role(
                self.model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd
            )
            == note_text
        )

    def test_insert_header_row_middle(self):
        # insert header row in middle of playlist

        note_text = "test text"
        insert_row = 6

        self.model.insert_row(proposed_row_number=insert_row, note=note_text)
        assert self.model.rowCount() == self.ROWS_TO_CREATE + 1
        prd = self.model.playlist_rows[insert_row]
        # Test against edit_role because display_role for headers is
        # handled differently (sets up row span)
        assert (
            self.model._edit_role(
                self.model.rowCount() - 1, playlistmodel.Col.NOTE.value, prd
            )
            == note_text
        )

    def test_add_track_to_header(self):
        note_text = "test text"
        insert_row = 6

        self.model.insert_row(proposed_row_number=insert_row, note=note_text)
        assert self.model.rowCount() == self.ROWS_TO_CREATE + 1

        prd = self.model.playlist_rows[1]
        self.model.add_track_to_header(insert_row, prd.track_id)

    def test_reverse_row_groups_one_row(self):
        rows_to_move = [3]

        result = self.model._reversed_contiguous_row_groups(rows_to_move)

        assert len(result) == 1
        assert result[0] == [3]

    def test_reverse_row_groups_multiple_row(self):
        rows_to_move = [2, 3, 4, 5, 7, 9, 10, 13, 17, 20, 21]

        result = self.model._reversed_contiguous_row_groups(rows_to_move)

        assert result == [[20, 21], [17], [13], [9, 10], [7], [2, 3, 4, 5]]

    def test_move_one_row_between_playlists_to_end(self):
        from_rows = [3]
        to_row = self.ROWS_TO_CREATE
        destination_playlist = "destination"

        model_src = self.model
        with db.Session() as session:
            playlist_dst = Playlists(session, destination_playlist, template_id=0)
            model_dst = playlistmodel.PlaylistModel(playlist_dst.id, is_template=False)
            for row in range(self.ROWS_TO_CREATE):
                model_dst.insert_row(proposed_row_number=row, note=str(row))

        model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
        model_dst.refresh_data(session)

        assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
        assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
        assert sorted([a.row_number for a in model_src.playlist_rows.values()]) == list(
            range(len(model_src.playlist_rows))
        )

    def test_move_one_row_between_playlists_to_middle(self):
        from_rows = [3]
        to_row = 2
        destination_playlist = "destination"

        model_src = self.model
        with db.Session() as session:
            playlist_dst = Playlists(session, destination_playlist, template_id=0)
            model_dst = playlistmodel.PlaylistModel(playlist_dst.id, is_template=False)
            for row in range(self.ROWS_TO_CREATE):
                model_dst.insert_row(proposed_row_number=row, note=str(row))

        model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
        model_dst.refresh_data(session)

        # Check the rows of the destination model
        row_notes = []
        for row_number in range(model_dst.rowCount()):
            index = model_dst.index(
                row_number, playlistmodel.Col.TITLE.value, QModelIndex()
            )
            row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole))

        assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
        assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
        assert [int(a) for a in row_notes] == [0, 1, 3, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    def test_move_multiple_rows_between_playlists_to_end(self):
        from_rows = [1, 3, 4]
        to_row = 2
        destination_playlist = "destination"

        model_src = self.model
        with db.Session() as session:
            playlist_dst = Playlists(session, destination_playlist, template_id=0)
            model_dst = playlistmodel.PlaylistModel(playlist_dst.id, is_template=False)
            for row in range(self.ROWS_TO_CREATE):
                model_dst.insert_row(proposed_row_number=row, note=str(row))

        model_src.move_rows_between_playlists(from_rows, to_row, model_dst.playlist_id)
        model_dst.refresh_data(session)

        # Check the rows of the destination model
        row_notes = []
        for row_number in range(model_dst.rowCount()):
            index = model_dst.index(
                row_number, playlistmodel.Col.TITLE.value, QModelIndex()
            )
            row_notes.append(model_dst.data(index, Qt.ItemDataRole.EditRole))

        assert len(model_src.playlist_rows) == self.ROWS_TO_CREATE - len(from_rows)
        assert len(model_dst.playlist_rows) == self.ROWS_TO_CREATE + len(from_rows)
        assert [int(a) for a in row_notes] == [
            0,
            1,
            1,
            3,
            4,
            2,
            3,
            4,
            5,
            6,
            7,
            8,
            9,
            10,
        ]


# # def test_edit_header(monkeypatch, session): # edit header row in middle of playlist

# #     monkeypatch.setattr(playlistmodel, "Session", session)
# #     note_text = "test text"
# #     initial_row_count = 11
# #     insert_row = 6

# #     model = create_model_with_playlist_rows(session, initial_row_count)
# #     model.insert_header_row(insert_row, note_text)
# #     assert model.rowCount() == initial_row_count + 1
# #     prd = model.playlist_rows[insert_row]
# #     # Test against edit_role because display_role for headers is
# #     # handled differently (sets up row span)
# #     assert (
# #         model.edit_role(model.rowCount(), playlistmodel.Col.NOTE.value, prd)
# #         == note_text
# #     )
