from pprint import pprint
from typing import Optional

from app.models import (
    Playlists,
    Tracks,
)
from PyQt6.QtCore import Qt, QModelIndex

from app.helpers import get_file_metadata
from app import playlistmodel
from dbconfig import scoped_session

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 create_model_with_tracks(session: scoped_session, name: Optional[str] = None) -> "playlistmodel.PlaylistModel":
    playlist = Playlists(session, name or "test playlist")
    model = playlistmodel.PlaylistModel(playlist.id)

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

    session.commit()
    return model


def create_model_with_playlist_rows(
    session: scoped_session, rows: int, name: Optional[str] = None
) -> "playlistmodel.PlaylistModel":
    playlist = Playlists(session, name or "test playlist")
    # Create a model
    model = playlistmodel.PlaylistModel(playlist.id)
    for row in range(rows):
        plr = model.insert_row(proposed_row_number=row, note=str(row))
        model.playlist_rows[plr.plr_rownum] = playlistmodel.PlaylistRowData(plr)

    session.commit()
    return model


def test_11_row_playlist(monkeypatch, session):
    # Create multirow playlist
    monkeypatch.setattr(playlistmodel, "Session", session)
    model = create_model_with_playlist_rows(session, 11)
    assert model.rowCount() == 11
    assert max(model.playlist_rows.keys()) == 10
    for row in range(model.rowCount()):
        assert row in model.playlist_rows
        assert model.playlist_rows[row].plr_rownum == row


def test_move_rows_test2(monkeypatch, session):
    # move row 3 to row 5
    monkeypatch.setattr(playlistmodel, "Session", session)
    model = create_model_with_playlist_rows(session, 11)
    model.move_rows([3], 5)
    # Check we have all rows and plr_rownums are correct
    for row in range(model.rowCount()):
        assert row in model.playlist_rows
        assert model.playlist_rows[row].plr_rownum == row
        if row not in [3, 4, 5]:
            assert model.playlist_rows[row].note == str(row)
        elif row == 3:
            assert model.playlist_rows[row].note == str(4)
        elif row == 4:
            assert model.playlist_rows[row].note == str(5)
        elif row == 5:
            assert model.playlist_rows[row].note == str(3)


def test_move_rows_test3(monkeypatch, session):
    # move row 4 to row 3

    monkeypatch.setattr(playlistmodel, "Session", session)

    model = create_model_with_playlist_rows(session, 11)
    model.move_rows([4], 3)

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


def test_move_rows_test4(monkeypatch, session):
    # move row 4 to row 2

    monkeypatch.setattr(playlistmodel, "Session", session)

    model = create_model_with_playlist_rows(session, 11)
    model.move_rows([4], 2)

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


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

    monkeypatch.setattr(playlistmodel, "Session", session)

    model = create_model_with_playlist_rows(session, 11)
    model.move_rows([1, 4, 5, 10], 8)

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


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

    monkeypatch.setattr(playlistmodel, "Session", session)

    model = create_model_with_playlist_rows(session, 11)
    model.move_rows([3, 6], 5)

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


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

    monkeypatch.setattr(playlistmodel, "Session", session)

    model = create_model_with_playlist_rows(session, 11)
    model.move_rows([3, 5, 6], 8)

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


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

    monkeypatch.setattr(playlistmodel, "Session", session)

    model = create_model_with_playlist_rows(session, 11)
    model.move_rows([7, 8, 10], 5)

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


def test_insert_header_row_end(monkeypatch, session):
    # insert header row at end of playlist

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

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


def test_insert_header_row_middle(monkeypatch, session):
    # insert 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_row(proposed_row_number=insert_row, note=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() - 1, playlistmodel.Col.NOTE.value, prd)
        == note_text
    )


def test_create_model_with_tracks(monkeypatch, session):
    monkeypatch.setattr(playlistmodel, "Session", session)
    model = create_model_with_tracks(session)
    assert len(model.playlist_rows) == len(test_tracks)


def test_timing_one_track(monkeypatch, session):
    START_ROW = 0
    END_ROW = 2

    monkeypatch.setattr(playlistmodel, "Session", session)
    model = create_model_with_tracks(session)

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

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


def test_insert_track_new_playlist(monkeypatch, session):
    # insert a track into a new playlist

    monkeypatch.setattr(playlistmodel, "Session", session)

    playlist = Playlists(session, "test playlist")
    # Create a model
    model = playlistmodel.PlaylistModel(playlist.id)

    track_path = test_tracks[0]
    metadata = get_file_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]
    assert (
        model.edit_role(model.rowCount() - 1, playlistmodel.Col.TITLE.value, prd)
        == metadata["title"]
    )


def test_reverse_row_groups_one_row(monkeypatch, session):
    monkeypatch.setattr(playlistmodel, "Session", session)

    rows_to_move = [3]

    model_src = create_model_with_playlist_rows(session, 5, name="source")
    result = model_src._reversed_contiguous_row_groups(rows_to_move)

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


def test_reverse_row_groups_multiple_row(monkeypatch, session):
    monkeypatch.setattr(playlistmodel, "Session", session)

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

    model_src = create_model_with_playlist_rows(session, 5, name="source")
    result = model_src._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(monkeypatch, session):
    monkeypatch.setattr(playlistmodel, "Session", session)

    create_rowcount = 5
    from_rows = [3]
    to_row = create_rowcount

    model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
    model_dst = create_model_with_playlist_rows(session, create_rowcount, name="destination")

    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) == create_rowcount - len(from_rows)
    assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
    assert sorted([a.plr_rownum for a in model_src.playlist_rows.values()]) == list(
        range(len(model_src.playlist_rows))
    )


def test_move_one_row_between_playlists_to_middle(monkeypatch, session):
    monkeypatch.setattr(playlistmodel, "Session", session)

    create_rowcount = 5
    from_rows = [3]
    to_row = 2

    model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
    model_dst = create_model_with_playlist_rows(session, create_rowcount, name="destination")

    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).value())

    assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
    assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
    assert [int(a) for a in row_notes] == [0, 1, 3, 2, 3, 4]


def test_move_multiple_rows_between_playlists_to_end(monkeypatch, session):
    monkeypatch.setattr(playlistmodel, "Session", session)

    create_rowcount = 5
    from_rows = [1, 3, 4]
    to_row = 2

    model_src = create_model_with_playlist_rows(session, create_rowcount, name="source")
    model_dst = create_model_with_playlist_rows(session, create_rowcount, name="destination")

    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).value())

    assert len(model_src.playlist_rows) == create_rowcount - len(from_rows)
    assert len(model_dst.playlist_rows) == create_rowcount + len(from_rows)
    assert [int(a) for a in row_notes] == [0, 1, 3, 4, 1, 2, 3, 4]


# 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
#     )
