from distutils.version import StrictVersion
from typing import List
from uuid import UUID

from flask import request
from sqlalchemy.util import OrderedSet

from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Component, Computer
from ereuse_devicehub.resources.enums import RatingSoftware, SnapshotSoftware
from ereuse_devicehub.resources.event.models import Event, ManualRate, Snapshot, TestDataStorage, \
    WorkbenchRate
from teal.resource import View


class EventView(View):
    def one(self, id: UUID):
        """Gets one event."""
        event = Event.query.filter_by(id=id).one()
        return self.schema.jsonify(event)


SUPPORTED_WORKBENCH = StrictVersion('11.0')


class SnapshotView(View):
    def post(self):
        """
        Performs a Snapshot.

        See `Snapshot` section in docs for more info.
        """
        s = request.get_json()
        # Note that if we set the device / components into the snapshot
        # model object, when we flush them to the db we will flush
        # snapshot, and we want to wait to flush snapshot at the end
        device = s.pop('device')  # type: Computer
        components = s.pop('components') \
            if s['software'] == SnapshotSoftware.Workbench else None  # type: List[Component]
        snapshot = Snapshot(**s)

        # Remove new events from devices so they don't interfere with sync
        events_device = set(e for e in device.events_one)
        device.events_one.clear()
        if components:
            events_components = tuple(set(e for e in c.events_one) for c in components)
            for component in components:
                component.events_one.clear()

        # noinspection PyArgumentList
        assert not device.events_one
        assert all(not c.events_one for c in components) if components else True
        db_device, remove_events = self.resource_def.sync.run(device, components)
        snapshot.device = db_device
        snapshot.events |= remove_events | events_device
        # commit will change the order of the components by what
        # the DB wants. Let's get a copy of the list so we preserve order
        ordered_components = OrderedSet(x for x in snapshot.components)

        for event in events_device:
            if isinstance(event, ManualRate):
                event.algorithm_software = RatingSoftware.Ereuse
                event.algorithm_version = StrictVersion('1.0')
            if isinstance(event, WorkbenchRate):
                # todo process workbench rate
                event.data_storage = 2
                event.graphic_card = 4
                event.processor = 1

        # Add the new events to the db-existing devices and components
        db_device.events_one |= events_device
        if components:
            for component, events in zip(ordered_components, events_components):
                component.events_one |= events
                snapshot.events |= events

        db.session.add(snapshot)
        db.session.commit()
        # todo we are setting snapshot dirty again with this components but
        # we do not want to update it.
        # The real solution is https://stackoverflow.com/questions/
        # 24480581/set-the-insert-order-of-a-many-to-many-sqlalchemy-
        # flask-app-sqlite-db?noredirect=1&lq=1
        snapshot.components = ordered_components
        ret = self.schema.jsonify(snapshot)  # transform it back
        ret.status_code = 201
        return ret


class TestHardDriveView(View):
    def post(self):
        t = request.get_json()  # type: dict
        # noinspection PyArgumentList
        test = TestDataStorage(snapshot_id=t.pop('snapshot'), device_id=t.pop('device'), **t)
        return test


class StressTestView(View):
    def post(self):
        t = request.get_json()  # type: dict