import copy
import logging

from textual.widgets import DataTable

import shovl.schemas as schemas
from shovl.providers.database import DatabaseProvider

from .service import ServiceScreen

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


class DatabaseScreen(ServiceScreen):
    """
    Screen to browse the contents of a database.
    """

    BINDINGS = [
        ("backspace", "back", "back"),
        ("f", "filter", "filter"),
        ("p", "preview", "preview"),
    ]

    ### Actions ###

    def action_preview(self) -> None:
        """
        Preview the selected database entity.
        """
        entity = self.get_selected_row()

        assert isinstance(entity, schemas.DatabaseEntity)

        if entity and entity.type in (
            schemas.DatabaseEntityType.table,
            schemas.DatabaseEntityType.view,
        ):
            self.run_in_thread(
                target=self.preview_worker,
                name="PreviewWorker",
                entity=entity,
            )

            self.current_filter = ""
        else:
            self.notify_info("Please select a table or view to preview.")

    def action_sql(self) -> None:
        """
        Open the SQL editor screen.
        """
        pass  # todo
        # from shovl.screens.sql import SqlScreen

        # self.app.push_screen(SqlScreen(config=self.config))

    ### Event handlers ###

    def on_mount(self) -> None:
        """
        Handle the screen mount event by initializing the table, i.e. liting
        availables schemas.
        """
        self.run_in_thread(
            target=self.inspection_worker,
            name="InspectionWorker",
            entity=schemas.DatabaseEntity(
                name="Schemas",
                type=schemas.DatabaseEntityType.schemas,
            ),
            add_current_to_history=False,
        )

    def on_data_table_cell_selected(
        self, event: DataTable.CellSelected
    ) -> None:
        """
        Handle Enter key on list items.
        """
        entity = self.get_selected_row()

        assert isinstance(entity, schemas.DatabaseEntity)

        if entity and entity.type in (
            schemas.DatabaseEntityType.schema,
            schemas.DatabaseEntityType.tables,
            schemas.DatabaseEntityType.views,
            schemas.DatabaseEntityType.table,
            schemas.DatabaseEntityType.view,
        ):
            self.run_in_thread(
                target=self.inspection_worker,
                name="InspectionWorker",
                entity=entity,
                add_current_to_history=True,
            )

    ### Class methods ###

    def inspect(
        self, entity: schemas.ServiceEntity, add_current_to_history: bool
    ) -> None:
        """
        Inspect a specific row in the table. This method is meant to be
        called from a separate thread to avoid blocking the UI.
        """
        assert isinstance(entity, schemas.DatabaseEntity)
        assert isinstance(self.provider, DatabaseProvider)

        match entity.type:
            case schemas.DatabaseEntityType.schemas:
                entities = self.provider.inspect_schemas()
            case schemas.DatabaseEntityType.schema:
                entities = [
                    schemas.DatabaseEntity(
                        name="tables",
                        type=schemas.DatabaseEntityType.tables,
                        dbschema=entity.name,
                    ),
                    schemas.DatabaseEntity(
                        name="views",
                        type=schemas.DatabaseEntityType.views,
                        dbschema=entity.name,
                    ),
                ]
            case schemas.DatabaseEntityType.tables:
                assert entity.dbschema
                entities = self.provider.inspect_tables(schema=entity.dbschema)
            case schemas.DatabaseEntityType.views:
                assert entity.dbschema
                entities = self.provider.inspect_views(schema=entity.dbschema)
            case schemas.DatabaseEntityType.table:
                assert entity.dbschema
                entities = self.provider.inspect_table(
                    table_name=entity.name, schema=entity.dbschema
                )
            case schemas.DatabaseEntityType.view:
                assert entity.dbschema
                entities = self.provider.inspect_view(
                    view_name=entity.name, schema=entity.dbschema
                )
            case _:
                raise Exception(f"Unsupported entity type: {entity.type}")

        self.all_entities = entities
        self.filtered_entities = copy.copy(self.all_entities)

        self.current_filter = ""

        if self.current_entity and add_current_to_history:
            self.add_to_history(self.current_entity)
        self.current_entity = entity

    def fetch_preview(self, entity: schemas.DatabaseEntity) -> None:
        """
        Preview a table or view. Meant to be called from a separate
        thread to avoid blocking the UI.
        """
        assert isinstance(self.provider, DatabaseProvider)
        assert entity.dbschema

        self.all_entities = self.provider.fetch_preview(
            schema=entity.dbschema or "",
            name=entity.name or "",
        )
        self.filtered_entities = copy.copy(self.all_entities)

        self.current_filter = ""

        if self.current_entity:
            self.add_to_history(self.current_entity)
        self.current_entity = entity

    def filter_view(self) -> None:
        """
        Filter database entities based on filter query. Meant to
        be called from a separate thread to avoid blocking the UI.
        """
        query = self.current_filter.lower().strip()

        if not query:
            self.filtered_entities = copy.copy(self.all_entities)
        else:
            self.filtered_entities = []
            for entity in self.all_entities:
                assert isinstance(entity, schemas.DatabaseEntity)
                if not entity.data and query in entity.name.lower():
                    self.filtered_entities.append(entity)
                    continue
                if not entity.data:
                    continue
                if any(
                    query in str(value).lower()
                    for value in entity.data.values()
                ):
                    self.filtered_entities.append(entity)

    def add_columns(self, entity: schemas.ServiceEntity) -> None:
        """
        Add columns to the table based on the type of entity.
        """
        assert isinstance(entity, schemas.DatabaseEntity)

        match entity.type:
            case (
                schemas.DatabaseEntityType.schemas
                | schemas.DatabaseEntityType.schema
                | schemas.DatabaseEntityType.tables
                | schemas.DatabaseEntityType.views
                | schemas.DatabaseEntityType.table
                | schemas.DatabaseEntityType.view
            ):
                self.table.add_columns("Name", "Type")
            case schemas.DatabaseEntityType.column:
                self.table.add_columns("Name", "Data Type", "Nullable", "Type")
            case schemas.DatabaseEntityType.row:
                assert entity.data is not None
                self.table.add_columns(*entity.data.keys())

    def add_row(self, entity: schemas.ServiceEntity) -> None:
        """
        Add a row to the table.
        """
        assert isinstance(entity, schemas.DatabaseEntity)

        name = self.get_entity_display_name(entity=entity)

        if entity.type in (
            schemas.DatabaseEntityType.schemas,
            schemas.DatabaseEntityType.schema,
            schemas.DatabaseEntityType.tables,
            schemas.DatabaseEntityType.views,
            schemas.DatabaseEntityType.table,
            schemas.DatabaseEntityType.view,
        ):
            self.table.add_row(name, entity.type.value)
        elif entity.type == schemas.DatabaseEntityType.column:
            self.table.add_row(
                name,
                entity.datatype or "",
                "True" if entity.nullable else "False",
                entity.type.value,
            )
        elif entity.type == schemas.DatabaseEntityType.row:
            assert entity.data is not None
            self.table.add_row(*[str(value) for value in entity.data.values()])

    def get_entity_display_name(self, entity: schemas.DatabaseEntity) -> str:
        """
        Get the display name for a database entity, including an icon if
        configured.
        """
        if entity.type == schemas.DatabaseEntityType.schemas:
            icon = self.config.gui.icons.schemas
        elif entity.type == schemas.DatabaseEntityType.schema:
            icon = self.config.gui.icons.dbschema
        elif entity.type == schemas.DatabaseEntityType.schema:
            icon = self.config.gui.icons.dbschema
        elif entity.type == schemas.DatabaseEntityType.tables:
            icon = self.config.gui.icons.tables
        elif entity.type == schemas.DatabaseEntityType.views:
            icon = self.config.gui.icons.views
        elif entity.type == schemas.DatabaseEntityType.table:
            icon = self.config.gui.icons.table
        elif entity.type == schemas.DatabaseEntityType.view:
            icon = self.config.gui.icons.view
        else:
            icon = ""

        if icon:
            return f"{icon} {entity.name}"
        else:
            return entity.name

    ### Workers ###

    def preview_worker(
        self,
        entity: schemas.DatabaseEntity,
    ) -> None:
        """
        Execute a database query.
        """
        try:
            self.app.call_from_thread(
                self.push_status_screen, label="Fetching preview..."
            )

            self.fetch_preview(entity=entity)

            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"Query 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"Preview failed: {message}")
            )
