import logging
from typing import List, Sequence

from textual.app import ComposeResult
from textual.widgets import DataTable, Footer, Header

import shovl.providers as providers
import shovl.schemas as schemas

from .base import BaseScreen

logger = logging.getLogger("shovl.screens.service")


class ServiceScreen(BaseScreen):
    """
    Base service screen. Inherited by all service screens (e.g. database,
    bucket, etc.) to provide common functionality such as displaying
    connection info, handling common keybindings, etc.
    """

    CSS_PATH = "../styles/service.tcss"

    table: DataTable

    connection: schemas.ServiceConfig
    provider: providers.ServiceProvider

    history: List[schemas.ServiceEntity]
    all_entities: Sequence[schemas.ServiceEntity]
    filtered_entites: Sequence[schemas.ServiceEntity]

    current_entity: schemas.ServiceEntity | None

    ### Lifecycle methods ###

    def __init__(
        self,
        config: schemas.ConfigSchema,
        connection: schemas.ServiceConfig,
        provider: providers.ServiceProvider,
    ) -> None:
        """
        Initialize the database screen.
        """
        super().__init__(config=config)
        self.connection = connection
        self.provider = provider

        self.history = []
        self.all_entities = []
        self.filtered_entities = []

        self.current_entity = None

        self.set_title(f"shovl - {self.connection.name}")

    def compose(self) -> ComposeResult:
        """
        Compose the service screen.
        """
        yield Header()
        self.table = DataTable(
            classes="table",
            id="table",
        )
        yield self.table
        yield Footer()

    ### Actions ###

    def action_back(self) -> None:
        """
        Go back one step in history, if available.
        """
        previous_entity = self.go_back_in_history()
        if previous_entity:
            self.run_in_thread(
                target=self.inspection_worker,
                name="InspectionWorker",
                entity=previous_entity,
                add_current_to_history=False,
            )
        else:
            self.provider.disconnect()
            self.app.pop_screen()

    ### Class methods ###

    def focus_view(self) -> None:
        """
        Focus the tabe.
        """
        self.table.focus()

    def add_columns(self, entity: schemas.ServiceEntity) -> None:
        """
        Add columns to the table based on the type of entity.
        """
        raise NotImplementedError(
            "add_columns must be implemented by subclasses"
        )

    def add_row(self, entity: schemas.ServiceEntity) -> None:
        """
        Add a row to the table.
        """
        raise NotImplementedError("add_rows must be implemented by subclasses")

    def refresh_view(self, show_truncated_hint: bool) -> None:
        """
        Add rows to the table based on a list of entities.
        """
        with self.app.batch_update():
            self.table.clear(columns=True)

            if not self.filtered_entities:
                super().refresh_view(show_truncated_hint=show_truncated_hint)
                return

            self.add_columns(entity=self.filtered_entities[0])

            for index, entity in enumerate(self.filtered_entities):
                if (
                    index >= self.config.gui.max_list_items
                    and self.config.gui.max_list_items > 0
                ):
                    if show_truncated_hint:
                        self.notify_info(
                            f"Showing first {self.config.gui.max_list_items} "
                            f"items. Refine your filter to see more."
                        )
                    break

                self.add_row(entity=entity)

        super().refresh_view(show_truncated_hint=show_truncated_hint)

    def get_selected_row(self) -> schemas.ServiceEntity | None:
        """
        Get the data of the currently selected row in the table and return it
        as a BucketEntity object.
        """
        if self.table.cursor_row is None:
            return None

        if self.table.cursor_row >= len(self.filtered_entities):
            return None

        return self.filtered_entities[self.table.cursor_row]

    def add_to_history(self, entity: schemas.ServiceEntity) -> None:
        """
        Add an item to the history stack.
        """
        self.history.append(entity)

    def go_back_in_history(self) -> schemas.ServiceEntity | None:
        """
        Go back in history and return the previous entity, or None if at root.
        """
        if len(self.history) >= 1:
            return self.history.pop()
        elif len(self.history) == 0:
            return None

    def inspect(
        self, entity: schemas.ServiceEntity, add_current_to_history: bool
    ) -> None:
        """
        Inspect a bucket entity and update the view with the results. This
        method is meant to be called from a separate thread to avoid blocking
        the UI.
        """
        raise NotImplementedError("inspect must be implemented by subclasses")

    ### Workers ###

    def inspection_worker(
        self, entity: schemas.BucketEntity, add_current_to_history: bool
    ) -> None:
        """
        Inspect a service entity.
        """
        try:
            self.app.call_from_thread(
                self.push_status_screen, label="Inspecting..."
            )

            self.inspect(
                entity=entity, add_current_to_history=add_current_to_history
            )

            self.app.call_from_thread(
                self.refresh_view, show_truncated_hint=True
            )

            self.app.call_from_thread(self.dismiss_status_screen)
        except Exception as exception:
            logger.exception(f"Inspection failed: {exception}")
            self.app.call_from_thread(self.dismiss_status_screen)
            self.app.call_from_thread(self.focus_view)
            message = str(exception)
            self.app.call_from_thread(
                lambda: self.notify_error(
                    f"Unable to inspect entity: {message}"
                )
            )
