import click, re
from typing import List

from textual.screen import Screen
from textual.widgets import Header, Footer, Static, Button, Input, DataTable, TextArea
from textual.containers import Grid

from ffx.model.pattern import Pattern

from .track_details_screen import TrackDetailsScreen
from .track_delete_screen import TrackDeleteScreen
from .shifted_season_delete_screen import ShiftedSeasonDeleteScreen
from .shifted_season_details_screen import ShiftedSeasonDetailsScreen

from .tag_details_screen import TagDetailsScreen
from .tag_delete_screen import TagDeleteScreen
from .screen_support import build_screen_bootstrap, build_screen_controllers

from ffx.track_type import TrackType

from ffx.track_disposition import TrackDisposition
from ffx.track_descriptor import TrackDescriptor

from textual.widgets._data_table import CellDoesNotExist

from ffx.file_properties import FileProperties
from ffx.iso_language import IsoLanguage
from ffx.audio_layout import AudioLayout
from ffx.model.shifted_season import ShiftedSeason

from ffx.helper import formatRichColor, removeRichColor


# Screen[dict[int, str, int]]
class PatternDetailsScreen(Screen):

    CSS = """

    Grid {
        grid-size: 7 20;
        grid-rows: 2 2 2 2 2 2 6 2 2 8 2 2 8 2 2 8 2 2 2 2;
        grid-columns: 25 25 25 25 25 25 25;
        height: 100%;
        width: 100%;
        padding: 1;
    }

    Input {
        border: none;
    }
    Button {
        border: none;
    }

    DataTable {
        min-height: 6;
    }

    DataTable .datatable--cursor {
        background: darkorange;
        color: black;
    }

    DataTable .datatable--header {
        background: steelblue;
        color: white;
    }

    #toplabel {
        height: 1;
    }
    
    .three {
        column-span: 3;
    }
    
    .four {
        column-span: 4;
    }
    .five {
        column-span: 5;
    }
    .six {
        column-span: 6;
    }
    .seven {
        column-span: 7;
    }


    .four_box {
        min-height: 6;
    }


    .box {
        height: 100%;
        border: solid green;
    }

    .yellow {
        tint: yellow 40%;
    }
    """

    def __init__(self, patternId = None, showId = None):
        super().__init__()

        bootstrap = build_screen_bootstrap(self.app.getContext())
        self.context = bootstrap.context

        self.__removeGlobalKeys = bootstrap.remove_global_keys
        self.__ignoreGlobalKeys = bootstrap.ignore_global_keys

        controllers = build_screen_controllers(
            self.context,
            pattern=True,
            show=True,
            track=True,
            tag=True,
            shifted_season=True,
        )
        self.__pc = controllers['pattern']
        self.__sc = controllers['show']
        self.__tc = controllers['track']
        self.__tac = controllers['tag']
        self.__ssc = controllers['shifted_season']

        self.__pattern : Pattern = self.__pc.getPattern(patternId) if patternId is not None else None
        self.__showDescriptor = self.__sc.getShowDescriptor(showId) if showId is not None else None
        self.__draftTracks : List[TrackDescriptor] = []
        self.__draftTags : dict[str, str] = {}


    def updateTracks(self):

        self.tracksTable.clear()

        tracks = self.getCurrentTrackDescriptors()

        typeCounter = {}

        td: TrackDescriptor
        for td in tracks:

            if (trackType := td.getType()) != TrackType.ATTACHMENT:

                if not trackType in typeCounter.keys():
                    typeCounter[trackType] = 0

                dispoSet = td.getDispositionSet()

                trackLanguage = td.getLanguage()
                audioLayout = td.getAudioLayout()

                row = (td.getIndex(),
                        trackType.label(),
                        typeCounter[trackType],
                        td.getCodec().label(),
                        audioLayout.label() if trackType == TrackType.AUDIO
                            and audioLayout != AudioLayout.LAYOUT_UNDEFINED else ' ',
                        trackLanguage.label() if trackLanguage != IsoLanguage.UNDEFINED else ' ',
                        td.getTitle(),
                        'Yes' if TrackDisposition.DEFAULT in dispoSet else 'No',
                        'Yes' if TrackDisposition.FORCED in dispoSet else 'No',
                        td.getSourceIndex())

                self.tracksTable.add_row(*map(str, row))

                typeCounter[trackType] += 1


    def getCurrentTrackDescriptors(self) -> List[TrackDescriptor]:
        if self.__pattern is not None:
            return self.__tc.findSiblingDescriptors(self.__pattern.getId())
        return list(self.__draftTracks)


    def normalizeDraftTracks(self):

        typeCounter = {}

        for index, trackDescriptor in enumerate(self.__draftTracks):
            trackDescriptor.setIndex(index)

            trackType = trackDescriptor.getType()
            subIndex = typeCounter.get(trackType, 0)
            trackDescriptor.setSubIndex(subIndex)
            typeCounter[trackType] = subIndex + 1

            if trackDescriptor.getSourceIndex() < 0:
                trackDescriptor.setSourceIndex(index)


    def swapTracks(self, trackIndex1: int, trackIndex2: int):

        ti1 = int(trackIndex1)
        ti2 = int(trackIndex2)

        if self.__pattern is None:
            numSiblings = len(self.__draftTracks)

            if ti1 < 0 or ti1 >= numSiblings:
                raise ValueError(f"PatternDetailsScreen.swapTracks(): trackIndex1 ({ti1}) is out of range ({numSiblings})")

            if ti2 < 0 or ti2 >= numSiblings:
                raise ValueError(f"PatternDetailsScreen.swapTracks(): trackIndex2 ({ti2}) is out of range ({numSiblings})")

            self.__draftTracks[ti1], self.__draftTracks[ti2] = self.__draftTracks[ti2], self.__draftTracks[ti1]
            self.normalizeDraftTracks()
            self.updateTracks()
            return

        siblingDescriptors: List[TrackDescriptor] = self.__tc.findSiblingDescriptors(self.__pattern.getId())

        numSiblings = len(siblingDescriptors)

        if ti1 < 0 or ti1 >= numSiblings:
            raise ValueError(f"PatternDetailsScreen.swapTracks(): trackIndex1 ({ti1}) is out of range ({numSiblings})")
        
        if ti2 < 0 or ti2 >= numSiblings:
            raise ValueError(f"PatternDetailsScreen.swapTracks(): trackIndex2 ({ti2}) is out of range ({numSiblings})")

        sibling1 = siblingDescriptors[trackIndex1]
        sibling2 = siblingDescriptors[trackIndex2]

        # raise click.ClickException(f"siblings id1={sibling1.getId()} id2={sibling2.getId()}")

        subIndex2 = sibling2.getSubIndex()
        
        sibling2.setIndex(sibling1.getIndex())
        sibling2.setSubIndex(sibling1.getSubIndex())

        sibling1.setIndex(trackIndex2)
        sibling1.setSubIndex(subIndex2)

        if not self.__tc.updateTrack(sibling1.getId(), sibling1):
            raise click.ClickException('Update sibling1 failed')
        if not self.__tc.updateTrack(sibling2.getId(), sibling2):
            raise click.ClickException('Update sibling2 failed')

        self.updateTracks()


    def updateTags(self):

        self.tagsTable.clear()

        tags = (
            self.__tac.findAllMediaTags(self.__pattern.getId())
            if self.__pattern is not None
            else self.__draftTags
        )

        for tagKey, tagValue in tags.items():

            textColor = None
            if tagKey in self.__ignoreGlobalKeys:
                textColor = 'blue'
            if tagKey in self.__removeGlobalKeys:
                textColor = 'red'

            row = (formatRichColor(tagKey, textColor), formatRichColor(tagValue, textColor))
            self.tagsTable.add_row(*map(str, row))

    def updateShiftedSeasons(self):

        self.shiftedSeasonsTable.clear()

        if self.__pattern is None:
            return

        shiftedSeason: ShiftedSeason
        for shiftedSeason in self.__ssc.getShiftedSeasonSiblings(patternId=self.__pattern.getId()):
            shiftedSeasonObj = shiftedSeason.getObj()

            firstEpisode = shiftedSeasonObj['first_episode']
            firstEpisodeStr = str(firstEpisode) if firstEpisode != -1 else ''

            lastEpisode = shiftedSeasonObj['last_episode']
            lastEpisodeStr = str(lastEpisode) if lastEpisode != -1 else ''

            row = (
                shiftedSeasonObj['original_season'],
                firstEpisodeStr,
                lastEpisodeStr,
                shiftedSeasonObj['season_offset'],
                shiftedSeasonObj['episode_offset'],
            )

            self.shiftedSeasonsTable.add_row(*map(str, row))

    def getSelectedShiftedSeasonObjFromInput(self):

        shiftedSeasonObj = {}

        try:
            row_key, col_key = self.shiftedSeasonsTable.coordinate_to_cell_key(
                self.shiftedSeasonsTable.cursor_coordinate
            )

            if row_key is not None:
                selected_row_data = self.shiftedSeasonsTable.get_row(row_key)

                def parse_int_or_default(value: str, default: int) -> int:
                    try:
                        return int(value)
                    except (TypeError, ValueError):
                        return default

                shiftedSeasonObj['original_season'] = int(selected_row_data[0])
                shiftedSeasonObj['first_episode'] = parse_int_or_default(selected_row_data[1], -1)
                shiftedSeasonObj['last_episode'] = parse_int_or_default(selected_row_data[2], -1)
                shiftedSeasonObj['season_offset'] = parse_int_or_default(selected_row_data[3], 0)
                shiftedSeasonObj['episode_offset'] = parse_int_or_default(selected_row_data[4], 0)

                if self.__pattern is not None:
                    shiftedSeasonId = self.__ssc.findShiftedSeason(
                        patternId=self.__pattern.getId(),
                        originalSeason=shiftedSeasonObj['original_season'],
                        firstEpisode=shiftedSeasonObj['first_episode'],
                        lastEpisode=shiftedSeasonObj['last_episode'],
                    )
                    if shiftedSeasonId is not None:
                        shiftedSeasonObj['id'] = shiftedSeasonId

        except CellDoesNotExist:
            pass

        return shiftedSeasonObj


    def on_mount(self):

        if not self.__showDescriptor is None:
            self.query_one("#showlabel", Static).update(f"{self.__showDescriptor.getId()} - {self.__showDescriptor.getName()} ({self.__showDescriptor.getYear()})")

        if self.__pattern is not None:

            self.query_one("#pattern_input", Input).value = str(self.__pattern.getPattern())

            if self.__pattern and self.__pattern.quality:
                self.query_one("#quality_input", Input).value = str(self.__pattern.quality)

            if self.__pattern and self.__pattern.notes:
                self.query_one("#notes_textarea", TextArea).text = str(self.__pattern.notes)

            self.updateTags()
            self.updateTracks()
            self.updateShiftedSeasons()

    def compose(self):


        self.tagsTable = DataTable(classes="seven")

        # Define the columns with headers
        self.column_key_tag_key = self.tagsTable.add_column("Key", width=50)
        self.column_key_tag_value = self.tagsTable.add_column("Value", width=100)

        self.tagsTable.cursor_type = 'row'


        self.tracksTable = DataTable(id="tracks_table", classes="seven")

        self.column_key_track_index = self.tracksTable.add_column("Index", width=5)
        self.column_key_track_type = self.tracksTable.add_column("Type", width=10)
        self.column_key_track_sub_index = self.tracksTable.add_column("SubIndex", width=8)
        self.column_key_track_codec = self.tracksTable.add_column("Codec", width=10)
        self.column_key_track_audio_layout = self.tracksTable.add_column("Layout", width=10)
        self.column_key_track_language = self.tracksTable.add_column("Language", width=15)
        self.column_key_track_title = self.tracksTable.add_column("Title", width=48)
        self.column_key_track_default = self.tracksTable.add_column("Default", width=8)
        self.column_key_track_forced = self.tracksTable.add_column("Forced", width=8)
        self.column_key_track_source_index = self.tracksTable.add_column("SrcIndex", width=8)

        self.tracksTable.cursor_type = 'row'

        self.shiftedSeasonsTable = DataTable(classes="seven")

        self.column_key_original_season = self.shiftedSeasonsTable.add_column("Source Season", width=18)
        self.column_key_first_episode = self.shiftedSeasonsTable.add_column("First Episode", width=18)
        self.column_key_last_episode = self.shiftedSeasonsTable.add_column("Last Episode", width=18)
        self.column_key_season_offset = self.shiftedSeasonsTable.add_column("Season Offset", width=18)
        self.column_key_episode_offset = self.shiftedSeasonsTable.add_column("Episode Offset", width=18)

        self.shiftedSeasonsTable.cursor_type = 'row'


        yield Header()

        with Grid():

            # 1
            yield Static("Edit filename pattern" if self.__pattern is not None else "New filename pattern", id="toplabel")
            yield Input(type="text", id="pattern_input", classes="six")

            # 2
            yield Static("from show")
            yield Static("", id="showlabel", classes="five")
            yield Button("Substitute pattern", id="pattern_button")

            # 3
            yield Static(" ", classes="seven")



            # 4
            yield Static("Quality")
            yield Input(type="integer", id="quality_input")
            yield Static(' ', classes="five")


            # 5
            yield Static(" ", classes="seven")


            # 6
            yield Static("Notes")
            yield Static(" ", classes="six")

            # 7
            yield TextArea(id="notes_textarea", classes="four_box seven")


            # 8
            yield Static(" ", classes="seven")

            # 9
            yield Static("Shifted Seasons")
            if self.__pattern is not None:
                yield Button("Add", id="button_add_shifted_season")
                yield Button("Edit", id="button_edit_shifted_season")
                yield Button("Delete", id="button_delete_shifted_season")
            else:
                yield Static(" ")
                yield Static(" ")
                yield Static(" ")

            yield Static(" ")
            yield Static(" ")
            yield Static(" ")

            # 10
            yield self.shiftedSeasonsTable

            # 11
            yield Static(" ", classes="seven")

            # 12
            yield Static("Media Tags")
            yield Button("Add", id="button_add_tag")
            yield Button("Edit", id="button_edit_tag")
            yield Button("Delete", id="button_delete_tag")

            yield Static(" ")
            yield Static(" ")
            yield Static(" ")

            # 13
            yield self.tagsTable

            # 14
            yield Static(" ", classes="seven")

            # 15
            yield Static("Streams")
            yield Button("Add", id="button_add_track")
            yield Button("Edit", id="button_edit_track")
            yield Button("Delete", id="button_delete_track")

            yield Static(" ")
            yield Button("Up", id="button_track_up")
            yield Button("Down", id="button_track_down")

            # 16
            yield self.tracksTable

            # 17
            yield Static(" ", classes="seven")

            # 18
            yield Static(" ", classes="seven")

            # 19
            yield Button("Save", id="save_button")
            yield Button("Cancel", id="cancel_button")
            yield Static(" ", classes="five")

            # 20
            yield Static(" ", classes="seven")

        yield Footer()


    def getPatternFromInput(self):
        return str(self.query_one("#pattern_input", Input).value)

    def getQualityFromInput(self):
        try:
            return int(self.query_one("#quality_input", Input).value)
        except ValueError:
            return 0
        
    def getNotesFromInput(self):
        return str(self.query_one("#notes_textarea", TextArea).text)


    def getSelectedTrackDescriptor(self):

        try:

            row_key, col_key = self.tracksTable.coordinate_to_cell_key(self.tracksTable.cursor_coordinate)

            if row_key is not None:
                selected_track_data = self.tracksTable.get_row(row_key)

                trackIndex = int(selected_track_data[0])
                trackSubIndex = int(selected_track_data[2])

                for trackDescriptor in self.getCurrentTrackDescriptors():
                    if (trackDescriptor.getIndex() == trackIndex
                            and trackDescriptor.getSubIndex() == trackSubIndex):
                        return trackDescriptor

            return None

        except CellDoesNotExist:
            return None



    def getSelectedTag(self):

        try:

            # Fetch the currently selected row when 'Enter' is pressed
            #selected_row_index = self.table.cursor_row
            row_key, col_key = self.tagsTable.coordinate_to_cell_key(self.tagsTable.cursor_coordinate)

            if row_key is not None:
                selected_tag_data = self.tagsTable.get_row(row_key)

                tagKey = removeRichColor(selected_tag_data[0])
                tagValue = removeRichColor(selected_tag_data[1])

                return tagKey, tagValue

            else:
                return None

        except CellDoesNotExist:
            return None



   # Event handler for button press
    def on_button_pressed(self, event: Button.Pressed) -> None:
        # Check if the button pressed is the one we are interested in
        if event.button.id == "save_button":

            patternDescriptor = {}
            patternDescriptor['show_id'] = self.__showDescriptor.getId()
            patternDescriptor['pattern'] = self.getPatternFromInput()
            patternDescriptor['quality'] = self.getQualityFromInput()
            patternDescriptor['notes'] = self.getNotesFromInput()

            if self.__pattern is not None:

                if self.__pc.updatePattern(self.__pattern.getId(), patternDescriptor):
                    self.dismiss(patternDescriptor)
                else:
                    #TODO: Meldung
                    self.app.pop_screen()

            else:
                patternId = self.__pc.savePatternSchema(
                    patternDescriptor,
                    trackDescriptors=self.__draftTracks,
                    mediaTags=self.__draftTags,
                )
                if patternId:
                    self.dismiss(patternDescriptor)
                else:
                    #TODO: Meldung
                    self.app.pop_screen()


        if event.button.id == "cancel_button":
            self.app.pop_screen()

        if event.button.id == "button_add_shifted_season":
            if self.__pattern is not None:
                self.app.push_screen(
                    ShiftedSeasonDetailsScreen(patternId=self.__pattern.getId()),
                    self.handle_update_shifted_season,
                )

        if event.button.id == "button_edit_shifted_season":
            selectedShiftedSeasonObj = self.getSelectedShiftedSeasonObjFromInput()
            if 'id' in selectedShiftedSeasonObj.keys():
                self.app.push_screen(
                    ShiftedSeasonDetailsScreen(
                        patternId=self.__pattern.getId(),
                        shiftedSeasonId=selectedShiftedSeasonObj['id'],
                    ),
                    self.handle_update_shifted_season,
                )

        if event.button.id == "button_delete_shifted_season":
            selectedShiftedSeasonObj = self.getSelectedShiftedSeasonObjFromInput()
            if 'id' in selectedShiftedSeasonObj.keys():
                self.app.push_screen(
                    ShiftedSeasonDeleteScreen(
                        patternId=self.__pattern.getId(),
                        shiftedSeasonId=selectedShiftedSeasonObj['id'],
                    ),
                    self.handle_delete_shifted_season,
                )


        numTracks = len(self.getCurrentTrackDescriptors())

        if event.button.id == "button_add_track":
            self.app.push_screen(
                TrackDetailsScreen(
                    patternId=self.__pattern.getId() if self.__pattern is not None else None,
                    patternLabel=self.getPatternFromInput(),
                    siblingTrackDescriptors=self.getCurrentTrackDescriptors(),
                    index=numTracks,
                ),
                self.handle_add_track,
            )

        selectedTrack = self.getSelectedTrackDescriptor()
        if selectedTrack is not None:
            if event.button.id == "button_edit_track":
                self.app.push_screen(
                    TrackDetailsScreen(
                        trackDescriptor=selectedTrack,
                        patternId=self.__pattern.getId() if self.__pattern is not None else None,
                        patternLabel=self.getPatternFromInput(),
                        siblingTrackDescriptors=self.getCurrentTrackDescriptors(),
                    ),
                    self.handle_edit_track,
                )
            if event.button.id == "button_delete_track":
                self.app.push_screen(
                    TrackDeleteScreen(trackDescriptor = selectedTrack),
                    self.handle_delete_track,
                )


        if event.button.id == "button_add_tag":
            self.app.push_screen(TagDetailsScreen(), self.handle_update_tag)

        if event.button.id == "button_edit_tag":
            selectedTag = self.getSelectedTag()
            if selectedTag is not None:
                tagKey, tagValue = selectedTag
                self.app.push_screen(TagDetailsScreen(key=tagKey, value=tagValue), self.handle_update_tag)

        if event.button.id == "button_delete_tag":
            selectedTag = self.getSelectedTag()
            if selectedTag is not None:
                tagKey, tagValue = selectedTag
                self.app.push_screen(TagDeleteScreen(key=tagKey, value=tagValue), self.handle_delete_tag)


        if event.button.id == "pattern_button":

            pattern = self.query_one("#pattern_input", Input).value

            patternMatch = re.search(FileProperties.SE_INDICATOR_PATTERN, pattern)

            if patternMatch:
                self.query_one("#pattern_input", Input).value = pattern.replace(patternMatch.group(1),
                                                                                FileProperties.SE_INDICATOR_PATTERN)


        if event.button.id == "button_track_up":

            selectedTrackDescriptor = self.getSelectedTrackDescriptor()
            if selectedTrackDescriptor is not None:
                selectedTrackIndex = selectedTrackDescriptor.getIndex()

                if selectedTrackIndex > 0 and selectedTrackIndex < self.tracksTable.row_count:
                    correspondingTrackIndex = selectedTrackIndex - 1
                    self.swapTracks(selectedTrackIndex, correspondingTrackIndex)


        if event.button.id == "button_track_down":

            selectedTrackDescriptor = self.getSelectedTrackDescriptor()
            if selectedTrackDescriptor is not None:
                selectedTrackIndex = selectedTrackDescriptor.getIndex()

                if selectedTrackIndex >= 0 and selectedTrackIndex < (self.tracksTable.row_count - 1):
                    correspondingTrackIndex = selectedTrackIndex + 1
                    self.swapTracks(selectedTrackIndex, correspondingTrackIndex)


    def handle_add_track(self, trackDescriptor : TrackDescriptor):
        if trackDescriptor is None:
            return

        if self.__pattern is not None:
            self.__tc.addTrack(trackDescriptor, patternId=self.__pattern.getId())
        else:
            self.__draftTracks.append(trackDescriptor)
            self.normalizeDraftTracks()

        self.updateTracks()


    def handle_edit_track(self, trackDescriptor : TrackDescriptor):
        if trackDescriptor is None:
            return

        if self.__pattern is not None:
            if not self.__tc.updateTrack(trackDescriptor.getId(), trackDescriptor):
                raise click.ClickException("PatternDetailsScreen.handle_edit_track(): track update failed")
        else:
            selectedTrack = self.getSelectedTrackDescriptor()
            for index, currentTrack in enumerate(self.__draftTracks):
                if (selectedTrack is not None
                        and currentTrack.getIndex() == selectedTrack.getIndex()
                        and currentTrack.getSubIndex() == selectedTrack.getSubIndex()):
                    self.__draftTracks[index] = trackDescriptor
                    break
            self.normalizeDraftTracks()

        self.updateTracks()


    def handle_delete_track(self, trackDescriptor : TrackDescriptor):
        if trackDescriptor is None:
            return

        if self.__pattern is not None:
            track = self.__tc.getTrack(trackDescriptor.getPatternId(), trackDescriptor.getIndex())

            if track is None:
                raise click.ClickException(
                    f"Track is none: patternId={trackDescriptor.getPatternId()} type={trackDescriptor.getType()} subIndex={trackDescriptor.getSubIndex()}"
                )

            self.__tc.deleteTrack(track.getId())
        else:
            self.__draftTracks = [
                currentTrack
                for currentTrack in self.__draftTracks
                if not (
                    currentTrack.getIndex() == trackDescriptor.getIndex()
                    and currentTrack.getSubIndex() == trackDescriptor.getSubIndex()
                )
            ]
            self.normalizeDraftTracks()

        self.updateTracks()



    def handle_update_tag(self, tag):
        if tag is None:
            return

        if self.__pattern is None:
            self.__draftTags[str(tag[0])] = str(tag[1])
        else:
            if self.__tac.updateMediaTag(self.__pattern.getId(), tag[0], tag[1]) is None:
                raise click.ClickException("PatternDetailsScreen.handle_update_tag(): tag update failed")

        self.updateTags()

    def handle_delete_tag(self, tag):
        if tag is None:
            return

        if self.__pattern is None:
            self.__draftTags.pop(str(tag[0]), None)
            self.updateTags()
            return

        if self.__tac.deleteMediaTagByKey(self.__pattern.getId(), tag[0]):
            self.updateTags()
        else:
            raise click.ClickException('tag delete failed')

    def handle_update_shifted_season(self, screenResult):
        self.updateShiftedSeasons()

    def handle_delete_shifted_season(self, screenResult):
        self.updateShiftedSeasons()
