# -*- coding: utf-8; -*-
#
# Copyright (c) 2016 Álan Crístoffer
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
Database class. Connects to MongoDB and abstracts all communication with it.
"""

import time
import uuid

from pymongo import MongoClient


class DatabaseV1(object):
    """
    Database class. Connects to MongoDB and abstracts all communication.
    """

    def __init__(self):
        self.client = MongoClient()
        self.db = self.client.moirai
        self.token_lifespan = 24 * 3600
        self.__migrate()
        self.__create_indexes()
        self.set_setting("version", "1.0")

    def close(self):
        self.client.close()

    def set_setting(self, key, value):
        db = self.db.settings
        db.replace_one({"key": key}, {"key": key, "value": value}, upsert=True)

    def get_setting(self, key):
        db = self.db.settings
        document = db.find_one({"key": key})
        if document:
            return document["value"]
        else:
            return None

    def verify_token(self, token):
        now = int(time.time())
        span = self.token_lifespan
        ts = self.get_setting("tokens")
        vs = [t for t in ts if t["token"] == token and now - t["time"] < span]
        if vs:
            ts = [t for t in ts if t["token"] != token]
            ts.append({"token": token, "time": now})
            ts = [t for t in ts if now - t["time"] < span]
            self.set_setting("tokens", ts)
            return True
        return False

    def generate_token(self):
        t = {"token": uuid.uuid4().hex, "time": time.time()}
        ts = self.get_setting("tokens") or []
        ts += [t]
        ts = [t for t in ts if time.time() - t["time"] < self.token_lifespan]
        self.set_setting("tokens", ts)
        return t["token"]

    def save_test(self, name, date):
        graph = {"name": name, "date": date}
        self.db.graphs.insert_one(graph)
        return graph["_id"]

    def save_test_sensor_value(self, graph_id, sensor, value, time):
        if isinstance(value, bool):
            value = int(value)
        elif not isinstance(value, int):
            value = float(value)
        data = {"sensor": sensor, "value": value, "time": time, "graph": graph_id}
        self.db.graphs_data.insert_one(data)

    def list_test_data(self):
        cursor = self.db.graphs.find()
        tests = [{"name": t["name"], "date": t["date"]} for t in cursor]
        return tests

    def get_test_data(self, test, start_time, skip=0):
        oid = self.db.graphs.find_one({"name": test, "date": start_time})["_id"]
        cursor = self.db.graphs_data.aggregate(
            [
                {"$match": {"graph": oid}},
                {"$sort": {"time": 1}},
                {"$skip": skip},
                {"$project": {"sensor": 1, "time": 1, "value": 1, "_id": 0}},
            ]
        )
        return list(cursor)

    def get_filtered_test_data(self, test, start_time, sensors):
        oid = self.db.graphs.find_one({"name": test, "date": start_time})["_id"]
        match = {"$match": {"graph": oid, "sensor": {"$in": sensors}}}
        sort = {"$sort": {"time": 1}}
        group = {
            "$group": {
                "_id": "$sensor",
                "values": {"$push": "$value"},
                "time": {"$push": "$time"},
            }
        }
        project = {"$project": {"time": 1, "sensor": "$_id", "values": 1, "_id": 0}}
        cursor = self.db.graphs_data.aggregate([match, sort, group, project])
        return list(cursor)

    def remove_test(self, test):
        tests = test if isinstance(test, list) else [test]
        for test in tests:
            oid = self.db.graphs.find_one(test)["_id"]
            self.db.graphs.delete_one({"_id": oid})
            self.db.graphs_data.delete_many({"graph": oid})

    def dump_database(self):
        graphs = list(self.db.graphs.find())
        for graph in graphs:
            graph["data"] = []
            for point in self.db.graphs_data.find({"graph": graph["_id"]}):
                del point["_id"]
                del point["graph"]
                graph["data"].append(point)
            del graph["_id"]
        settings = self.db.settings.find({}, {"_id": 0})
        return list(settings), list(graphs)

    def restore_database_v2(self, settings, graphs):
        self.db.settings.drop()
        self.db.graphs.drop()
        self.db.graphs_data.drop()
        self.db.settings.insert_many(settings)
        for graph in graphs:
            g = {"name": graph["name"], "date": graph["date"]}
            self.db.graphs.insert_one(g)
            for point in graph["data"]:
                point["graph"] = g["_id"]
            self.db.graphs_data.insert_many(graph["data"])
        self.set_setting("version", "1.0")

    def restore_database_v1(self, settings, test_sensor_values):
        self.db.settings.drop()
        self.db.graphs.drop()
        self.db.graphs_data.drop()
        self.db.settings.insert_many(settings)
        self.db.test_sensor_values.insert_many(test_sensor_values)
        self.__migrate()

    def __create_indexes(self):
        self.db.graphs_data.create_index("time", name="time")
        self.db.graphs_data.create_index("graph", name="graph")

    def __migrate(self):
        if self.get_setting("version") is None:
            match = {"$match": {"time": {"$lt": 1}}}
            group = {"$group": {"_id": {"name": "$test", "date": "$start_time"}}}
            project = {"$project": {"_id": 0, "name": "$_id.name", "date": "$_id.date"}}
            query = [match, group, project]
            cursor = self.db.test_sensor_values.aggregate(query)
            tests = list(cursor)
            if tests:
                self.db.graphs.insert_many(tests)
                for test in tests:
                    oid = test["_id"]
                    match = {
                        "$match": {"test": test["name"], "start_time": test["date"]}
                    }
                    project = {
                        "$project": {
                            "_id": 0,
                            "time": 1,
                            "sensor": 1,
                            "value": 1,
                            "graph": oid,
                        }
                    }
                    query = [match, project]
                    cursor = self.db.test_sensor_values.aggregate(query)
                    self.db.graphs_data.insert_many(cursor)
                self.db.test_sensor_values.drop()
                self.set_setting("version", "1.0")
