from enum import Enum
from sqlite3.dbapi2 import Date

import psycopg2
from psycopg2.extras import RealDictCursor

from blueprints.RoadmapAPI import RoadmapParameters
from blueprints.MilestoneAPI import MilestoneParameters
from blueprints.TaskAPI import TaskParameters
from blueprints.SubTaskAPI import SubTaskParameters
from logic.DatabaseMigrator import DatabaseMigrator


class FetchType(Enum):
    NONE = 1
    ONE = 2
    ALL = 3


class Database:
    VERSION = 2

    def __init__(self, database_settings):
        self.__host = database_settings["host"]
        self.__port = database_settings["port"]
        self.__database = database_settings["databaseName"]
        self.__user = database_settings["user"]
        self.__password = database_settings["password"]

        self.__connection = None
        self.__cursor = None

        self.__connect()

        self.__create_tables()

    def __connect(self):
        self.__connection = psycopg2.connect(user=self.__user,
                                             password=self.__password,
                                             host=self.__host,
                                             port=self.__port,
                                             database=self.__database)
        self.__connection.autocommit = True
        self.__cursor = self.__connection.cursor(cursor_factory=RealDictCursor)

    def __disconnect(self):
        if self.__connection is not None:
            self.__connection.close()

    def _query(self, query, *args, fetch_type=FetchType.ALL):
        try:
            self.__cursor.execute(query, args)
        except psycopg2.InterfaceError:
            self.__disconnect()
            self.__connect()
            self.__cursor.execute(query, args)

        if fetch_type == FetchType.ONE:
            return self.__cursor.fetchone()
        if fetch_type == FetchType.ALL:
            return self.__cursor.fetchall()

    def __create_tables(self):
        self.__create_table_version()
        self.__create_table_roadmaps()
        self.__create_table_milestones()
        self.__create_table_tasks()
        self.__create_table_subtasks()

        databaseMigrator = DatabaseMigrator(self)
        databaseMigrator.migrate()

    def __create_table_version(self):
        queryTable = f'CREATE TABLE IF NOT EXISTS "public"."version" ' \
                     f'("version" int4 NOT NULL DEFAULT {self.VERSION});'
        self._query(queryTable, fetch_type=FetchType.NONE)

        if not self.get_version():
            queryInsert = f'INSERT INTO version ("version") VALUES (%s);'
            self._query(queryInsert, self.VERSION, fetch_type=FetchType.NONE)

    def __create_table_roadmaps(self):
        query = 'CREATE SEQUENCE IF NOT EXISTS "roadmaps_ID_seq"'
        self._query(query, fetch_type=FetchType.NONE)

        queryTable = f'CREATE TABLE IF NOT EXISTS "public"."roadmaps" ' \
                     f'("{RoadmapParameters.ID.value}" int4 NOT NULL DEFAULT ' \
                     f'nextval(\'"roadmaps_ID_seq"\'::regclass),' \
                     f'"{RoadmapParameters.PROJECT_NAME.value}" text NOT NULL, ' \
                     f'"{RoadmapParameters.HIDDEN.value}" boolean NOT NULL DEFAULT false, ' \
                     f'PRIMARY KEY ("{RoadmapParameters.ID.value}"));'
        self._query(queryTable, fetch_type=FetchType.NONE)

    def __create_table_milestones(self):
        query = 'CREATE SEQUENCE IF NOT EXISTS "milestones_ID_seq"'
        self._query(query, fetch_type=FetchType.NONE)

        queryTable = f'CREATE TABLE IF NOT EXISTS "public"."milestones" ' \
                     f'("{MilestoneParameters.ID.value}" int4 NOT NULL DEFAULT ' \
                     f'nextval(\'"milestones_ID_seq"\'::regclass),' \
                     f'"{MilestoneParameters.ROADMAP_ID.value}" int4 NOT NULL, ' \
                     f'"{MilestoneParameters.VERSION_CODE.value}" int4 NOT NULL, ' \
                     f'"{MilestoneParameters.VERSION_NAME.value}" text NOT NULL, ' \
                     f'"{MilestoneParameters.TITLE.value}" text NOT NULL, ' \
                     f'"{MilestoneParameters.DUE_DATE.value}" date NOT NULL, ' \
                     f'"{MilestoneParameters.COMPLETION_DATE.value}" date NOT NULL, ' \
                     f'"{MilestoneParameters.STATUS.value}" int4 NOT NULL, ' \
                     f'PRIMARY KEY ("{MilestoneParameters.ID.value}"));'
        self._query(queryTable, fetch_type=FetchType.NONE)

    def __create_table_tasks(self):
        query = 'CREATE SEQUENCE IF NOT EXISTS "tasks_ID_seq"'
        self._query(query, fetch_type=FetchType.NONE)

        queryTable = f'CREATE TABLE IF NOT EXISTS "public"."tasks" ' \
                     f'("{TaskParameters.ID.value}" int4 NOT NULL DEFAULT ' \
                     f'nextval(\'"tasks_ID_seq"\'::regclass),' \
                     f'"{TaskParameters.MILESTONE_ID.value}" int4 NOT NULL, ' \
                     f'"{TaskParameters.TITLE.value}" text NOT NULL, ' \
                     f'"{TaskParameters.DESCRIPTION.value}" text NOT NULL, ' \
                     f'"{TaskParameters.STATUS.value}" int4 NOT NULL, ' \
                     f'PRIMARY KEY ("{TaskParameters.ID.value}"));'
        self._query(queryTable, fetch_type=FetchType.NONE)

    def __create_table_subtasks(self):
        query = 'CREATE SEQUENCE IF NOT EXISTS "subtasks_ID_seq"'
        self._query(query, fetch_type=FetchType.NONE)

        queryTable = f'CREATE TABLE IF NOT EXISTS "public"."subtasks" ' \
                     f'("{SubTaskParameters.ID.value}" int4 NOT NULL DEFAULT ' \
                     f'nextval(\'"subtasks_ID_seq"\'::regclass),' \
                     f'"{SubTaskParameters.TASK_ID.value}" int4 NOT NULL, ' \
                     f'"{SubTaskParameters.TITLE.value}" text NOT NULL, ' \
                     f'"{SubTaskParameters.DESCRIPTION.value}" text NOT NULL, ' \
                     f'"{SubTaskParameters.STATUS.value}" int4 NOT NULL, ' \
                     f'PRIMARY KEY ("{SubTaskParameters.ID.value}"));'
        self._query(queryTable, fetch_type=FetchType.NONE)

# VERSION
    def get_version(self) -> int or None:
        query = f'SELECT * FROM version;'
        result = self._query(query, fetch_type=FetchType.ONE)
        if result is None:
            return None

        return result['version']

    def update_version(self, version: int):
        query = f'UPDATE version SET "version"=%s;'
        self._query(query, version, fetch_type=FetchType.NONE)

# ROADMAPS
    def get_roadmaps(self):
        query = f'SELECT * FROM roadmaps ORDER BY "{RoadmapParameters.ID.value}";'
        return self._query(query)

    def get_visible_roadmaps(self):
        query = f'SELECT * FROM roadmaps WHERE "{RoadmapParameters.HIDDEN.value}"=FALSE ORDER BY "{RoadmapParameters.ID.value}";'
        return self._query(query)

    def get_roadmap(self, roadmapID):
        query = f'SELECT * FROM roadmaps WHERE "{RoadmapParameters.ID.value}"=%s;'
        return self._query(query, roadmapID, fetch_type=FetchType.ONE)

    def add_roadmap(self, name):
        query = f'INSERT INTO roadmaps ("{RoadmapParameters.PROJECT_NAME.value}") VALUES (%s);'
        self._query(query, name, fetch_type=FetchType.NONE)

    def update_roadmap(self, roadmapID, name, hidden):
        query = f'UPDATE roadmaps SET "{RoadmapParameters.PROJECT_NAME.value}"=%s, "{RoadmapParameters.HIDDEN.value}"=%s WHERE "{RoadmapParameters.ID.value}"=%s;'
        self._query(query, name, hidden, roadmapID, fetch_type=FetchType.NONE)

    def delete_roadmap(self, roadmapID):
        query = f'DELETE FROM roadmaps WHERE "{RoadmapParameters.ID.value}"=%s;'
        self._query(query, roadmapID, fetch_type=FetchType.NONE)

    # MILESTONES
    def get_all_milestones(self):
        query = f'SELECT * FROM milestones ORDER BY "{MilestoneParameters.VERSION_CODE.value}" DESC;'
        return self._query(query)

    def get_milestones(self, roadmapID):
        query = f'SELECT * FROM milestones WHERE "{MilestoneParameters.ROADMAP_ID.value}"=%s ORDER BY "{MilestoneParameters.VERSION_CODE.value}" DESC;'
        return self._query(query, roadmapID)

    def get_open_milestones(self, roadmapID):
        query = f'SELECT * FROM milestones WHERE "{MilestoneParameters.ROADMAP_ID.value}"=%s AND "Status"=0 ORDER BY "{MilestoneParameters.VERSION_CODE.value}" DESC;'
        return self._query(query, roadmapID)

    def get_milestone(self, milestoneID):
        query = f'SELECT * FROM milestones WHERE "{MilestoneParameters.ID.value}"=%s;'
        return self._query(query, milestoneID, fetch_type=FetchType.ONE)

    def get_latest_milestone(self, roadmapID):
        query = f'SELECT * FROM milestones WHERE "{MilestoneParameters.ROADMAP_ID.value}"=%s AND "{MilestoneParameters.STATUS.value}" = 1 ORDER BY "{MilestoneParameters.VERSION_CODE.value}" DESC;'
        return self._query(query, roadmapID, fetch_type=FetchType.ONE)

    def add_milestone(self, roadmapID, versionCode, versionName, title, dueDate, completionDate, status):
        query = f'INSERT INTO milestones ("{MilestoneParameters.ROADMAP_ID.value}", "{MilestoneParameters.VERSION_CODE.value}", "{MilestoneParameters.VERSION_NAME.value}", "{MilestoneParameters.TITLE.value}", "{MilestoneParameters.DUE_DATE.value}", "{MilestoneParameters.COMPLETION_DATE.value}", "{MilestoneParameters.STATUS.value}") VALUES (%s, %s, %s, %s, %s, %s, %s);'
        self._query(query, roadmapID, versionCode, versionName, title, dueDate, completionDate, status,
                    fetch_type=FetchType.NONE)

    def update_milestone(self, milestoneID, roadmapID, versionCode, versionName, title, dueDate, completionDate,
                         status):
        query = f'UPDATE milestones SET "{MilestoneParameters.ROADMAP_ID.value}"=%s, "{MilestoneParameters.VERSION_CODE.value}"=%s, "{MilestoneParameters.VERSION_NAME.value}"=%s, "{MilestoneParameters.TITLE.value}"=%s, "{MilestoneParameters.DUE_DATE.value}"=%s, "{MilestoneParameters.COMPLETION_DATE.value}"=%s, "{MilestoneParameters.STATUS.value}"=%s WHERE "{MilestoneParameters.ID.value}"=%s;'
        self._query(query, roadmapID, versionCode, versionName, title, dueDate, completionDate, status, milestoneID,
                    fetch_type=FetchType.NONE)

    def finish_milestone(self, milestoneID):
        query = f'UPDATE milestones SET "{MilestoneParameters.COMPLETION_DATE.value}"=%s, "{MilestoneParameters.STATUS.value}"=%s WHERE "{MilestoneParameters.ID.value}"=%s;'
        self._query(query, Date.today(), 1, milestoneID, fetch_type=FetchType.NONE)

    def delete_milestone(self, milestoneID):
        query = f'DELETE FROM milestones WHERE "{MilestoneParameters.ID.value}"=%s;'
        self._query(query, milestoneID, fetch_type=FetchType.NONE)

    # TASKS
    def get_all_tasks(self):
        query = f'SELECT * FROM tasks ORDER BY "{TaskParameters.ID.value}";'
        return self._query(query)

    def get_tasks(self, milestoneID):
        query = f'SELECT * FROM tasks WHERE "{TaskParameters.MILESTONE_ID.value}"=%s ORDER BY "{TaskParameters.ID.value}";'
        return self._query(query, milestoneID)

    def get_open_tasks(self, milestoneID):
        query = f'SELECT * FROM tasks WHERE "{TaskParameters.MILESTONE_ID.value}"=%s AND "{TaskParameters.STATUS.value}"=0 ORDER BY "{TaskParameters.ID.value}";'
        return self._query(query, milestoneID)

    def get_task(self, taskID):
        query = f'SELECT * FROM tasks WHERE "{TaskParameters.ID.value}"=%s;'
        return self._query(query, taskID, fetch_type=FetchType.ONE)

    def add_task(self, milestoneID, title, description, status):
        query = f'INSERT INTO tasks ("{TaskParameters.MILESTONE_ID.value}", "{TaskParameters.TITLE.value}", "{TaskParameters.DESCRIPTION.value}", "{TaskParameters.STATUS.value}") VALUES (%s, %s, %s, %s);'
        self._query(query, milestoneID, title, description, status, fetch_type=FetchType.NONE)

    def update_task(self, taskID, milestoneID, title, description, status):
        query = f'UPDATE tasks SET "{TaskParameters.MILESTONE_ID.value}"=%s, "{TaskParameters.TITLE.value}"=%s, "{TaskParameters.DESCRIPTION.value}"=%s, "{TaskParameters.STATUS.value}"=%s WHERE "{TaskParameters.ID.value}"=%s;'
        self._query(query, milestoneID, title, description, status, taskID, fetch_type=FetchType.NONE)

    def finish_task(self, taskID):
        query = f'UPDATE tasks SET "{TaskParameters.STATUS.value}"=%s WHERE "{TaskParameters.ID.value}"=%s;'
        self._query(query, 1, taskID, fetch_type=FetchType.NONE)

    def delete_task(self, taskID):
        query = f'DELETE FROM tasks WHERE "{TaskParameters.ID.value}"=%s;'
        self._query(query, taskID, fetch_type=FetchType.NONE)

    # SUBTASKS
    def get_all_sub_tasks(self):
        query = f'SELECT * FROM subtasks ORDER BY "{SubTaskParameters.ID.value}";'
        return self._query(query)

    def get_sub_tasks(self, taskID):
        query = f'SELECT * FROM subtasks WHERE "{SubTaskParameters.TASK_ID.value}"=%s ORDER BY "{SubTaskParameters.ID.value}";'
        return self._query(query, taskID)

    def get_open_sub_tasks(self, taskID):
        query = f'SELECT * FROM subtasks WHERE "{SubTaskParameters.TASK_ID.value}"=%s AND "{SubTaskParameters.STATUS.value}"=0 ORDER BY "{SubTaskParameters.ID.value}";'
        return self._query(query, taskID)

    def get_sub_task(self, subTaskID):
        query = f'SELECT * FROM subtasks WHERE "{SubTaskParameters.ID.value}"=%s;'
        return self._query(query, subTaskID, fetch_type=FetchType.ONE)

    def add_sub_task(self, taskID, title, description, status):
        query = f'INSERT INTO subtasks ("{SubTaskParameters.TASK_ID.value}", "{SubTaskParameters.TITLE.value}", "{SubTaskParameters.DESCRIPTION.value}", "{SubTaskParameters.STATUS.value}") VALUES (%s, %s, %s, %s);'
        self._query(query, taskID, title, description, status, fetch_type=FetchType.NONE)

    def update_sub_task(self, subTaskID, taskID, title, description, status):
        query = f'UPDATE subtasks SET "{SubTaskParameters.TASK_ID.value}"=%s, "{SubTaskParameters.TITLE.value}"=%s, "{SubTaskParameters.DESCRIPTION.value}"=%s, "{SubTaskParameters.STATUS.value}"=%s WHERE "{SubTaskParameters.ID.value}"=%s;'
        self._query(query, taskID, title, description, status, subTaskID, fetch_type=FetchType.NONE)

    def finish_sub_task(self, subTaskID):
        query = f'UPDATE subtasks SET "{SubTaskParameters.STATUS.value}"=%s WHERE "{SubTaskParameters.ID.value}"=%s;'
        self._query(query, 1, subTaskID, fetch_type=FetchType.NONE)

    def delete_sub_task(self, subTaskID):
        query = f'DELETE FROM subtasks WHERE "{SubTaskParameters.ID.value}"=%s;'
        self._query(query, subTaskID, fetch_type=FetchType.NONE)