from datetime import datetime
from enum import auto, Enum
from typing import Optional, TYPE_CHECKING

from PyQt6.QtCore import (
    QAbstractTableModel,
    QModelIndex,
    Qt,
    QVariant,
)

from PyQt6.QtGui import (
    QBrush,
    QColor,
    QFont,
)

from config import Config

from dbconfig import Session

from helpers import (
    file_is_unreadable,
)

from models import PlaylistRows, Tracks

if TYPE_CHECKING:
    from musicmuster import MusicMusterSignals


class Col(Enum):
    START_GAP = 0
    TITLE = auto()
    ARTIST = auto()
    DURATION = auto()
    START_TIME = auto()
    END_TIME = auto()
    LAST_PLAYED = auto()
    BITRATE = auto()
    NOTE = auto()


HEADER_NOTES_COLUMN = 1


class PlaylistRowData:
    def __init__(self, plr: PlaylistRows) -> None:
        """
        Populate PlaylistRowData from database PlaylistRows record
        """

        self.start_gap: Optional[int] = None
        self.title: str = ""
        self.artist: str = ""
        self.duration: int = 0
        self.lastplayed: datetime = Config.EPOCH
        self.bitrate = 0
        self.path = ""

        self.plrid: int = plr.id
        self.plr_rownum: int = plr.plr_rownum
        self.note: str = plr.note
        if plr.track:
            self.start_gap = plr.track.start_gap
            self.title = plr.track.title
            self.artist = plr.track.artist
            self.duration = plr.track.duration
            if plr.track.playdates:
                self.lastplayed = max([a.lastplayed for a in plr.track.playdates])
            else:
                self.lastplayed = Config.EPOCH
            self.bitrate = plr.track.bitrate or 0
            self.path = plr.track.path

    def __repr__(self) -> str:
        return (
            f"<PlaylistRowData: plrid={self.plrid}, plr_rownum={self.plr_rownum}, "
            f"note='{self.note}', title='{self.title}', artist='{self.artist}'>"
        )


class PlaylistModel(QAbstractTableModel):
    def __init__(
        self, playlist_id: int, signals: "MusicMusterSignals", *args, **kwargs
    ):
        self.playlist_id = playlist_id
        self.signals = signals
        super().__init__(*args, **kwargs)

        self.playlist_rows: dict[int, PlaylistRowData] = {}

        self.refresh_data()

    def __repr__(self) -> str:
        return f"<PlaylistModel: playlist_id={self.playlist_id}>"

    def background_role(self, row: int, column: int, prd: PlaylistRowData) -> QBrush:
        """Return background setting"""

        # Handle entire row colouring
        # Header row
        if not prd.path:
            return QBrush(QColor(Config.COLOUR_NOTES_PLAYLIST))
        # Unreadable track file
        if file_is_unreadable(prd.path):
            return QBrush(QColor(Config.COLOUR_UNREADABLE))

        if column == Col.START_GAP.value:
            if prd.start_gap and prd.start_gap >= Config.START_GAP_WARNING_THRESHOLD:
                return QBrush(QColor(Config.COLOUR_LONG_START))
        if column == Col.BITRATE.value:
            if prd.bitrate < Config.BITRATE_LOW_THRESHOLD:
                return QBrush(QColor(Config.COLOUR_BITRATE_LOW))
            elif prd.bitrate and prd.bitrate < Config.BITRATE_OK_THRESHOLD:
                return QBrush(QColor(Config.COLOUR_BITRATE_MEDIUM))
            else:
                return QBrush(QColor(Config.COLOUR_BITRATE_OK))

        return QBrush()

    def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
        """Standard function for view"""

        return 9

    def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
        """Return data to view"""

        if not index.isValid() or not (0 <= index.row() < len(self.playlist_rows)):
            return QVariant()

        row = index.row()
        column = index.column()
        # prd for playlist row data as it's used a lot
        prd = self.playlist_rows[row]

        # Dispatch to role-specific functions
        if role == Qt.ItemDataRole.DisplayRole:
            return self.display_role(row, column, prd)
        elif role == Qt.ItemDataRole.DecorationRole:
            pass
        elif role == Qt.ItemDataRole.EditRole:
            return self.edit_role(row, column, prd)
        elif role == Qt.ItemDataRole.ToolTipRole:
            pass
        elif role == Qt.ItemDataRole.StatusTipRole:
            pass
        elif role == Qt.ItemDataRole.WhatsThisRole:
            pass
        elif role == Qt.ItemDataRole.SizeHintRole:
            pass
        elif role == Qt.ItemDataRole.FontRole:
            pass
        elif role == Qt.ItemDataRole.TextAlignmentRole:
            pass
        elif role == Qt.ItemDataRole.BackgroundRole:
            return self.background_role(row, column, prd)
        elif role == Qt.ItemDataRole.ForegroundRole:
            pass
        elif role == Qt.ItemDataRole.CheckStateRole:
            pass
        elif role == Qt.ItemDataRole.InitialSortOrderRole:
            pass

        # Fall through to no-op
        return QVariant()

    def display_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
        """
        Return text for display
        """

        # Detect whether this is a header row
        if not prd.path:
            header = prd.note
        else:
            header = ""

        if header:
            if column == HEADER_NOTES_COLUMN:
                self.signals.span_cells_signal.emit(
                    row, HEADER_NOTES_COLUMN, 1, self.columnCount() - 1
                )
                return QVariant(prd.note)
            else:
                return QVariant()

        if column == Col.START_GAP.value:
            return QVariant(prd.start_gap)
        if column == Col.TITLE.value:
            return QVariant(prd.title)
        if column == Col.ARTIST.value:
            return QVariant(prd.artist)
        if column == Col.DURATION.value:
            return QVariant(prd.duration)
        if column == Col.START_TIME.value:
            return QVariant("FIXME")
        if column == Col.END_TIME.value:
            return QVariant("FIXME")
        if column == Col.LAST_PLAYED.value:
            return QVariant(prd.lastplayed)
        if column == Col.BITRATE.value:
            return QVariant(prd.bitrate)
        if column == Col.NOTE.value:
            return QVariant(prd.note)

        return QVariant()

    def edit_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
        """
        Return text for editing
        """

        if column == Col.TITLE.value:
            return QVariant(prd.title)
        if column == Col.ARTIST.value:
            return QVariant(prd.artist)
        if column == Col.NOTE.value:
            return QVariant(prd.note)

        return QVariant()

    def flags(self, index: QModelIndex) -> Qt.ItemFlag:
        """
        Standard model flags
        """

        if not index.isValid():
            return Qt.ItemFlag.ItemIsDropEnabled

        default = (
            Qt.ItemFlag.ItemIsEnabled
            | Qt.ItemFlag.ItemIsSelectable
            | Qt.ItemFlag.ItemIsDragEnabled
        )
        if index.column() in [Col.TITLE.value, Col.ARTIST.value, Col.NOTE.value]:
            return default | Qt.ItemFlag.ItemIsEditable

        return default

    def headerData(
        self,
        section: int,
        orientation: Qt.Orientation,
        role: int = Qt.ItemDataRole.DisplayRole,
    ) -> QVariant:
        """
        Return text for headers
        """

        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                if section == Col.START_GAP.value:
                    return QVariant(Config.HEADER_START_GAP)
                elif section == Col.TITLE.value:
                    return QVariant(Config.HEADER_TITLE)
                elif section == Col.ARTIST.value:
                    return QVariant(Config.HEADER_ARTIST)
                elif section == Col.DURATION.value:
                    return QVariant(Config.HEADER_DURATION)
                elif section == Col.START_TIME.value:
                    return QVariant(Config.HEADER_START_TIME)
                elif section == Col.END_TIME.value:
                    return QVariant(Config.HEADER_END_TIME)
                elif section == Col.LAST_PLAYED.value:
                    return QVariant(Config.HEADER_LAST_PLAYED)
                elif section == Col.BITRATE.value:
                    return QVariant(Config.HEADER_BITRATE)
                elif section == Col.NOTE.value:
                    return QVariant(Config.HEADER_NOTE)
            else:
                if Config.ROWS_FROM_ZERO:
                    return QVariant(str(section))
                else:
                    return QVariant(str(section + 1))

        elif role == Qt.ItemDataRole.FontRole:
            boldfont = QFont()
            boldfont.setBold(True)
            return QVariant(boldfont)

        return QVariant()

    def refresh_data(self):
        """Populate dicts for data calls"""

        # Populate self.playlist_rows with playlist data
        with Session() as session:
            for p in PlaylistRows.deep_rows(session, self.playlist_id):
                self.playlist_rows[p.plr_rownum] = PlaylistRowData(p)

    def refresh_row(self, session, row_number):
        """Populate dict for one row for data calls"""

        p = PlaylistRows.deep_row(session, self.playlist_id, row_number)
        self.playlist_rows[p.plr_rownum] = PlaylistRowData(p)

    def rowCount(self, index: QModelIndex = QModelIndex()) -> int:
        """Standard function for view"""

        return len(self.playlist_rows)

    def setData(
        self, index: QModelIndex, value: QVariant, role: int = Qt.ItemDataRole.EditRole
    ) -> bool:
        """
        Update model with edited data
        """

        if index.isValid() and role == Qt.ItemDataRole.EditRole:
            row_number = index.row()
            column = index.column()

            with Session() as session:
                plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
                if not plr:
                    print(
                        f"Error saving data: {row_number=}, {column=}, "
                        f"{value=}, {self.playlist_id=}"
                    )
                    return False

                if column == Col.TITLE.value or column == Col.ARTIST.value:
                    track = session.get(Tracks, plr.track_id)
                    if not track:
                        print(f"Error retreiving track: {plr=}")
                        return False
                    if column == Col.TITLE.value:
                        track.title = str(value)
                    elif column == Col.ARTIST.value:
                        track.artist = str(value)
                    else:
                        print(f"Error updating track: {column=}, {value=}")
                        return False
                elif column == Col.NOTE.value:
                    plr.note = str(value)

                # Flush changes before refreshing data
                session.flush()
                self.refresh_row(session, row_number)
                self.dataChanged.emit(index, index, [Qt.ItemDataRole.DisplayRole, role])
                return True

        return False

    def supportedDropActions(self):
        return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction
