import os
import sqlite3

from sqlite3 import Connection
from typing import (
    Any,
    Callable,
    List,
    Optional,
    Tuple,
    Union
)

from dbapitransactioncallback import DbApiTransactionCallback
from dbapitemplate import DbApiTemplate
from repository import FiscalRepository as BaseFiscalRepository


class SqliteFiscalRepository(BaseFiscalRepository):

    def __init__(
        self,
        database_dir,           # type: str
        backup_database_dir,    # type: str
        schema_sql_path,        # type: str
        callback                # type: DbApiTransactionCallback
    ):
        # type: (...) -> None

        self.callback = callback
        self.database_path = os.path.join(database_dir, "fiscal_persistcomp.db")
        self.backup_database_dir = backup_database_dir
        self.schema_sql_path = schema_sql_path

        self.fiscal_conn = None
        self.template = None
        self.sale_types = None

    def _get_db_api_template(
            self
    ):
        # type: () -> None

        if self.template is not None:
            return

        fiscal_conn = self._get_fiscal_conn()
        self.template = DbApiTemplate(fiscal_conn)
        self._update_callback_conn(fiscal_conn)

    def _get_fiscal_conn(
        self
    ):
        # type: () -> Connection

        if self.fiscal_conn is None:
            self.fiscal_conn = sqlite3.connect(self.database_path, check_same_thread=False)

        return self.fiscal_conn

    def _update_callback_conn(
        self,
        conn    # type: Connection
    ):
        # type: (...) -> None

        self.callback.update_conn(conn)

    @staticmethod
    def _execute_script(
        connection,     # type: Connection
        query           # type: str
    ):
        # type: (...) -> None

        connection.executescript(query)

    def _execute_template(
        self,
        query,              # type: str
        params=None,        # type: Tuple
        mapper=None,        # type: Optional[Callable[[tuple], Any]]
        is_script=False     # type: Optional[bool]
    ):
        # type: (...) -> Union[int, list]

        self._get_db_api_template()

        if is_script:
            return self._execute_script(
                connection=self.template.conn,
                query=query
            )
        return self.template.execute(
            sql=query,
            params=params if params else (), mapper=mapper
        )

    def get_orders(
        self,
        end_date    # type: int
    ):
        # type: (...) -> List[Tuple[int, str]]

        query = self._get_orders_by_date_query

        params = (end_date,) if end_date else None

        def mapper(row):
            return row["OrderId"], row["DataNota"]

        return self._execute_template(
            query=query,
            params=params,
            mapper=mapper
        )

    def _get_create_tables_query(self):
        # type: () -> str

        try:
            with open(self.schema_sql_path, "r") as f:
                query = f.read()
            return query
        except Exception as _:
            return ""

    def _create_tables_fiscals(self, db_api):
        # type: (DbApiTemplate) -> None

        query = self._get_table_exists_query
        _, data = db_api.execute(query, ())

        if not data:
            create_table_query = self._get_create_tables_query()
            self._execute_script(
                connection=db_api.conn,
                query=create_table_query
            )

    def fill_fiscal_backup_database(
        self,
        suffix_date,    # type: str
        orders          # type: List[int]
    ):
        # type: (...) -> None

        conn = sqlite3.connect(os.path.join(self.backup_database_dir, "fiscal_{}.db".format(suffix_date)))
        self._update_callback_conn(conn)
        db_api = DbApiTemplate(conn)

        self._create_tables_fiscals(db_api)

        query = self._backup_fiscal_database_query.format(self.database_path, ','.join([str(o) for o in orders]))

        self._execute_script(
            connection=db_api.conn,
            query=query
        )

    def delete_fiscals(
        self,
        orders      # type: List[int]
    ):
        # type: (...) -> None

        query = self._get_delete_orders_query.format(','.join([str(o) for o in orders]))
        self._execute_template(
            query=query,
            is_script=True
        )

    _get_table_exists_query = """
        SELECT count(name) FROM sqlite_master WHERE type='table' AND name='FiscalData'
    """

    _get_orders_by_date_query = """
        SELECT OrderId, DataNota
        FROM FiscalData
        WHERE SentToNfce = 1 AND ? > DataNota
        ORDER BY OrderId LIMIT 1000
    """

    _backup_fiscal_database_query = """
        ATTACH DATABASE '{0}' AS f; 
        INSERT OR REPLACE INTO FiscalData SELECT * FROM f.FiscalData WHERE OrderId in ({1});
        INSERT OR REPLACE INTO PaymentData SELECT * FROM f.PaymentData WHERE OrderId in ({1});
        DETACH f;
    """

    _get_delete_orders_query = """
        DELETE FROM FiscalData WHERE OrderId in ({0});
        DELETE FROM PaymentData WHERE OrderId in ({0});
    """
