#
# Aura Engine API (https://gitlab.servus.at/aura/engine-api)
#
# Copyright (C) 2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


import datetime

from sqlalchemy         import create_engine, Column, DateTime, String, Integer, Boolean
from sqlalchemy.event   import listen
from flask_sqlalchemy   import SQLAlchemy
from flask_marshmallow  import Marshmallow
from marshmallow        import Schema, fields, post_dump

db = SQLAlchemy()
ma = Marshmallow()


class PlayLog(db.Model):
    """
    Table holding play-log entries.
    """
    __tablename__ = 'playlog'

    # Primary Key
    track_start             = Column(DateTime, primary_key=True)

    # Columns
    track_artist            = Column(String(256))
    track_album             = Column(String(256))
    track_title             = Column(String(256))
    track_duration          = Column(String(256))
    track_type              = Column(Integer)
    timeslot_id             = Column(Integer)
    show_name               = Column(String(256))
    log_source              = Column(Integer)       # The play-out source which this log is coming from (e.g. engine1, engine2)
    is_synced               = Column(Boolean)       # Only relevant for main nodes, in a multi-node setup



    def __init__(self, data):
        """
        Initializes a trackservice entry
        """
        self.track_start     = data.track_start
        self.track_artist    = data.track_artist
        self.track_album     = data.track_album
        self.track_title     = data.track_title
        self.track_duration  = data.track_duration
        self.track_type      = data.track_type
        self.timeslot_id     = data.timeslot_id
        self.show_name       = data.show_name
        self.log_source      = data.log_source      
        self.is_synced       = False                


    def save(self):
        db.session.add(self)
        db.session.commit()


    @staticmethod
    def select_current():
        """
        Selects the currently playing track.
        """
        db.session.commit()
        now = datetime.datetime.now()
        track = db.session.query(PlayLog).\
            filter(PlayLog.track_start <= str(now)).\
            order_by(PlayLog.track_start.desc()).first()
        return track


    @staticmethod
    def paginate(page, page_size, from_time=None, to_time=None, skip_synced=False):
        """
        Returns a list of entries for a given page and an start time (optional).
        """
        def q(page=0, page_size=None):
            query = db.session.query(PlayLog).order_by(PlayLog.track_start.desc())
            if isinstance(from_time, datetime.datetime):
                query = query.filter(PlayLog.track_start >= from_time.isoformat(' ', 'seconds'))
            if isinstance(to_time, datetime.datetime):
                query = query.filter(PlayLog.track_start <= to_time.isoformat(' ', 'seconds'))
            if skip_synced == True:
                query = query.filter(PlayLog.is_synced == False)
            listen(query, 'before_compile', apply_limit(page, page_size), retval=True)
            print("Paginate Query: " + str(query))
            return query

        def apply_limit(page, page_size):
            def wrapped(query):
                if page_size:
                    query = query.limit(page_size)
                    if page:
                        query = query.offset(page * page_size)
                return query
            return wrapped

        return q(page, page_size)


    @staticmethod
    def select_last_hours(n):
        """
        Selects the tracks playing in the past (`n`) hours.
        """
        db.session.commit()
        last_hours = datetime.datetime.today() - datetime.timedelta(hours=n)
        tracks = db.session.query(PlayLog).filter(PlayLog.track_start >= str(last_hours)).\
            order_by(PlayLog.track_start.desc()).all()
        return tracks


    @staticmethod
    def select_by_day(day):
        """
        Select the track-service items for a day.
        """
        db.session.commit()
        day_plus_one = day + datetime.timedelta(days=1)
        tracks = db.session.query(PlayLog).\
            filter(PlayLog.track_start >= str(day), PlayLog.track_start < str(day_plus_one)).\
            order_by(PlayLog.track_start.desc()).all()
        return tracks
    

    @staticmethod
    def select_by_range(from_day, to_day):
        """
        Selects the track-service items for a day range.
        """
        db.session.commit()
        tracks = db.session.query(PlayLog).filter(PlayLog.track_start >= str(from_day),\
            PlayLog.track_start < str(to_day)).order_by(PlayLog.track_start.desc()).all()
        return tracks


    def __str__(self):
        return "Track [track_start: %s, track_title: %s]" % (str(self.track_start), str(self.track_title))



class PlayLogSchema(ma.SQLAlchemyAutoSchema):
    """
    Schema for playlog entries.
    """
    class Meta:
        model = PlayLog
        sqla_session = db.session

    SKIP_VALUES = set([None])

    @post_dump
    def remove_skip_values(self, data, many=False):
        return {
            key: value for key, value in data.items()
            if value not in self.SKIP_VALUES
        }


class TrackSchema(ma.SQLAlchemySchema):
    """
    Schema for trackservice entries.
    """
    class Meta:
        model = PlayLog
        sqla_session = db.session
        fields = (
            "track_start",
            "track_artist",
            "track_album",
            "track_title"
            )



class ActivityLog(db.Model):
    """
    Table holding a log of play-out source activity and their sync states.

    Only used in "SYNC" deployment mode.
    """
    __tablename__ = 'activity_log'

    # Primary Key
    log_time             = Column(DateTime, primary_key=True)

    # Columns
    source_number        = Column(Integer)
    is_synced            = Column(Boolean)


    def __init__(self, source_number):
        """
        Initializes an activity entry
        """
        self.log_time = datetime.datetime.now()
        self.source_number = source_number
        self.is_synced = False


    @staticmethod
    def is_empty():
        """
        Checks if the tables is empty.
        """
        db.session.commit()
        return not db.session.query(ActivityLog).one_or_none()


    @staticmethod
    def get_active_source():
        """
        Retrieves the currently active source.
        """
        db.session.commit()
        source = db.session.query(ActivityLog).\
            order_by(ActivityLog.log_time.desc()).first()
        return source


    def save(self):
        db.session.add(self)
        db.session.commit()



class HealthHistory(db.Model):
    """
    Table holding an history of health information for sources.
    """
    __tablename__ = 'health_history'

    # Primary Key
    log_time             = Column(DateTime, primary_key=True)

    # Columns
    log_source           = Column(Integer)          # The source the history entry relates to
    is_healthy           = Column(Boolean)          # Indicates if source is "healthy enough" to be used for play-out
    is_synced            = Column(Boolean)          # Only relevant for main nodes, in a multi-node setup
    health_info          = Column(String(2048))     # Stringified JSON object or other, if needed


    def __init__(self, source_number, is_healthy, health_info):
        """
        Initializes an health entry.
        """
        self.log_time = datetime.datetime.now()
        self.log_source = source_number
        self.is_healthy = is_healthy
        self.is_synced = False
        self.health_info = health_info


    @staticmethod
    def get_latest_entry(source_number):
        """
        Retrieves the most recent health history entry for the given source number.
        """
        return db.session.query(HealthHistory).filter(HealthHistory.log_source == source_number).first()


    def save(self):
        db.session.add(self)
        db.session.commit()



class HealthHistorySchema(ma.SQLAlchemyAutoSchema):
    """
    Schema for health history entries.
    """
    class Meta:
        model = HealthHistory
        sqla_session = db.session