from libservice.db.db import db_object
from libservice.base.entity import Entity
import json

class FacadeContent:
    """
    Allow the users management into the database
    """

    # Define the requests for the following function(s)
    REQUEST_CONTENT_LIST = "SELECT content_id, title, CAST(popularity AS CHAR), " \
                           "director, genre, CAST(imdb_score AS CHAR) FROM content"

    @staticmethod
    def get_list(search=None):

        query = FacadeContent.REQUEST_CONTENT_LIST

        if search is not None:
            query += " WHERE MATCH(title, genre, director) AGAINST('"+search+"*' IN BOOLEAN MODE ) "

        cursor = db_object.query(query)
        my_list = {}

        result_set = cursor.fetchall()

        for content in result_set:

            my_list[content[0]] = {}
            my_list[content[0]]['title'] = content[1]
            my_list[content[0]]['popularity'] = content[2]
            my_list[content[0]]['director'] = content[3]
            my_list[content[0]]['genre'] = content[4]
            my_list[content[0]]['imdb_score'] = content[5]

        return my_list

    ###########################################################################################

    # Define the requests for the following function(s)
    REQUEST_INSERT_CONTENT_LIST = "INSERT INTO content (title, popularity, director, genre, imdb_score) " \
                                  "VALUES (%s, %s, %s, %s, %s);"

    @staticmethod
    def add(entity):

        # Make sure all pending values are initialized
        entity.replace_all_none_by_empty()

        db_object.query(FacadeContent.REQUEST_INSERT_CONTENT_LIST, (entity['title'],
                                                                    entity['popularity'],
                                                                    entity['director'],
                                                                    json.dumps(entity['genre']),
                                                                    entity['imdb_score'],))
        db_object.commit_query()

###########################################################################################

    # Define the requests for the following function(s)
    REQUEST_EXIST_CONTENT_ITEM = "SELECT content_id FROM content WHERE content_id = lower(%s)"

    @staticmethod
    def get_content_by_id(content_item_id):
        """
        Verify is a content item with the requested id exist in the database

        :param content_item_id: The content_item_id on which we have to perform the verification [String, Required]
        :return: True if found.
        :return: False if not found.
        """

        cursor = db_object.query(FacadeContent.REQUEST_EXIST_CONTENT_ITEM, (int(content_item_id),))
        result_set = cursor.fetchall()
        return result_set

    #######################################################################################

    # Define the requests for the following function(s)
    REQUEST_DELETE_CONTENT_ITEM = """DELETE FROM content WHERE content_id = %s;"""

    @staticmethod
    def delete(content_item_id):
        """
        Delete's a Content item with id

        :param content_item_id: The content item id to remove [String, Required]
        :return: True if deleted.
        :return: False if not deleted.
        """

        db_object.query(FacadeContent.REQUEST_DELETE_CONTENT_ITEM,(int(content_item_id),))

    #######################################################################################

    # Define the requests for the following function(s)
    REQUEST_UPDATE_CONTENT_ITEM = """UPDATE content SET title = %s, popularity = %s, director = %s, genre = %s,
    imdb_score = %s WHERE content_id = %s"""

    @staticmethod
    def update(content_item_id, entity):
        """
        Update a Content item with id

        :param content_item_id: The content item id to update [String, Required]
        :param entity: The content data to update [String, Required]
        :return: True if deleted.
        :return: False if not deleted.
        """

        db_object.query(FacadeContent.REQUEST_UPDATE_CONTENT_ITEM, (entity['title'],
                                                                    entity['popularity'],
                                                                    entity['director'],
                                                                    json.dumps(entity['genre']),
                                                                    entity['imdb_score'],
                                                                    int(content_item_id),))

    #######################################################################################

    # Define the requests for the following function(s)
    REQUEST_GET_CONTENT_ITEM = """SELECT title, popularity, director, genre, imdb_score FROM content
    WHERE content_id = %s"""

    @staticmethod
    def get_content_info_by_id(content_item_id):
        """
        Fetch a Content item with id

        :param content_item_id: The content item id to update [String, Required]
        :return: Entity of content item information.
        """

        cursor = db_object.query(FacadeContent.REQUEST_GET_CONTENT_ITEM, (content_item_id,))

        result_set = cursor.fetchall()

        if result_set:
            return Entity({'title': result_set[0][0],
                           'popularity': float(result_set[0][1]),
                           'director': result_set[0][2],
                           'genre': result_set[0][3],
                           'imdb_score': float(result_set[0][4])
                           })
        return None


from fastapi import status, Depends, HTTPException, Response, APIRouter
from typing import List, Optional

from .. import schemas, oauth2
from ..db.connect import db_connect

conn, cursor = db_connect()

router = APIRouter(
    prefix="/posts",
    tags=["Posts"]
)

@router.get("/")
def get_posts(current_user = Depends(oauth2.get_current_user), limit: int = 10, search: Optional[str] = ""):

    key="%"+search+"%"

    # cursor.execute(""" SELECT posts.id, posts.title, posts.content, posts.published, posts.content, posts.created_at, posts.user_id, users.email FROM posts LEFT JOIN users ON posts.user_id = users.id WHERE posts.title ILIKE %s LIMIT(%s) """, (key, str(limit)))

    cursor.execute(""" select posts.*, users.email, count(votes.user_id) as likes from posts left join votes on
    posts.id = votes.post_id
    left join users on
    posts.user_id = users.id
    group by posts.id, users.email
    HAVING posts.title ILIKE %s limit(%s) """,
    (key, str(limit)))

    posts = cursor.fetchall()
    return posts

@router.post("/", status_code=status.HTTP_201_CREATED)
def set_post(post: schemas.PostCreate, current_user = Depends(oauth2.get_current_user)):
    user_id = current_user["id"]

    # %s to sanitize the data
    cursor.execute(""" INSERT INTO posts (title, content, published, user_id) VALUES (%s, %s, %s, %s) RETURNING * """, (post.title, post.content, post.published, user_id))

    new_post = cursor.fetchone()
    conn.commit()

    return new_post

@router.get('/{id}')
def get_post(id: int, current_user = Depends(oauth2.get_current_user)):

    cursor.execute(""" SELECT POSTS.*, users.email, count(votes.user_id) as likes FROM posts
    JOIN users ON posts.id = %s AND posts.user_id = users.id
    left join votes on
    posts.id = votes.post_id
    group by posts.id, users.email; """,
    (str(id),))

    post = cursor.fetchone()

    if not post:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id {id} not found")
    return post

@router.delete('/{id}', status_code=status.HTTP_204_NO_CONTENT)
def delete_post(id: int, current_user = Depends(oauth2.get_current_user)):

    cursor.execute("DELETE FROM posts WHERE id = %s RETURNING *", (str(id),))
    deleted_post = cursor.fetchone()
    conn.commit()

    if deleted_post == None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id {id} does not exist")

    if deleted_post["user_id"] != current_user["id"]:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,detail="Not authorized to perform requested action")

    return Response(status_code=status.HTTP_204_NO_CONTENT)

@router.put('/{id}', response_model=schemas.Post)
def update_post(id: int, post: schemas.PostCreate, current_user = Depends(oauth2.get_current_user)):

    cursor.execute("UPDATE posts SET title = %s, content = %s, published = %s WHERE id = %s RETURNING *", (post.title, post.content, post.published, str(id)))
    updated_post = cursor.fetchone()
    conn.commit()

    if updated_post == None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id {id} does not exist")

    if updated_post["user_id"] != current_user["id"]:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,detail="Not authorized to perform requested action")

    return updated_post


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
from datetime import datetime
from typing import Callable
from threading import Timer, Event

class ReentrantTimer(Timer):
    """
    Call a function after a specified number of seconds
    and can reentrant start
    always need a thread to run timer.

    t = Timer(30.0, f, args=None, kwargs=None)
    t.start_timer()
    ...
    t.start_timer()  # <- reentrant for reset
    ...
    t.stop_timer()
    t.start_timer()  # <- also reentrant
    ...
    t.cancel()       # <- exit thread
    """

    def __init__(self, interval: float | int, function: Callable, args=None, kwargs=None):
        Timer.__init__(self, interval, function, args, kwargs)
        self.idling = Event()
        self.running = True

    def start_timer(self):
        self.finished.clear()
        self.idling.set()
        if not self.is_alive():
            self.start()

    def stop_timer(self):
        self.idling.clear()
        self.finished.set()

    def is_active(self):
        return self.idling.is_set()

    def cancel(self):
        self.running = False
        self.finished.set()
        self.idling.set()

    def run(self):
        while self.running:
            self.idling.wait()
            super().run()
            self.idling.clear()

def now():
    return datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")

def printIt(*args, **kwargs):
    print(now(), *args, **kwargs)

def timer_event():
    printIt("Timer Event Here!")

def main():
    timer = ReentrantTimer(5, timer_event)
    printIt("Timer start.")
    timer.start_timer()
    printIt("Sleep 2s.")
    printIt("ACTIVE:", timer.is_active())
    time.sleep(2)
    printIt("Timer stop.")
    timer.stop_timer()
    printIt("Sleep 2s.")
    printIt("ACTIVE:", timer.is_active())
    time.sleep(2)
    printIt("Timer start.")
    timer.start_timer()
    printIt("Sleep 6s.")
    time.sleep(6)
    printIt("ACTIVE:", timer.is_active())
    printIt("Timer stop. (it already stopped)")
    timer.stop_timer()
    printIt("Sleep 6s.")
    time.sleep(6)
    printIt("ACTIVE:", timer.is_active())
    printIt("Timer Start.")
    timer.start_timer()
    printIt("Sleep 2s.")
    time.sleep(2)
    printIt("Timer Cancelled.")
    timer.cancel()
    printIt("Reached end.")
    printIt("Alive? ", timer.is_alive())

if __name__ == "__main__":
    main()