# -*- coding: utf-8 -*-
from sqlalchemy.orm import Session
from sqlalchemy import inspect
from sqlalchemy.orm.unitofwork import UOWTransaction
from transaction import TransactionManager
from contextlib import contextmanager

from tracim.exceptions import ContentRevisionDeleteError
from tracim.exceptions import ContentRevisionUpdateError
from tracim.exceptions import SameValueError

from tracim.models.data import ContentRevisionRO
from tracim.models.data import Content
from tracim.models.meta import DeclarativeBase


def prevent_content_revision_delete(
        session: Session,
        flush_context: UOWTransaction,
        instances: [DeclarativeBase]
) -> None:
    for instance in session.deleted:
        if isinstance(instance, ContentRevisionRO) \
                and instance.revision_id is not None:
            raise ContentRevisionDeleteError(
                "ContentRevision is not deletable. " +
                "You must make a new revision with" +
                "is_deleted set to True. Look at " +
                "tracim.model.new_revision context " +
                "manager to make a new revision"
            )


class RevisionsIntegrity(object):
    """
    Simple static used class to manage a list with list of ContentRevisionRO
    who are allowed to be updated.

    When modify an already existing (understood have an identity in databse)
    ContentRevisionRO, if it's not in RevisionsIntegrity._updatable_revisions
    list, a ContentRevisionUpdateError thrown.

    This class is used by tracim.model.new_revision context manager.
    """
    _updatable_revisions = []

    @classmethod
    def add_to_updatable(cls, revision: 'ContentRevisionRO') -> None:
        if inspect(revision).has_identity:
            raise ContentRevisionUpdateError("ContentRevision is not updatable. %s already have identity." % revision)  # nopep8

        if revision not in cls._updatable_revisions:
            cls._updatable_revisions.append(revision)

    @classmethod
    def remove_from_updatable(cls, revision: 'ContentRevisionRO') -> None:
        if revision in cls._updatable_revisions:
            cls._updatable_revisions.remove(revision)

    @classmethod
    def is_updatable(cls, revision: 'ContentRevisionRO') -> bool:
        return revision in cls._updatable_revisions


@contextmanager
def new_revision(
        session: Session,
        tm: TransactionManager,
        content: Content,
        force_create_new_revision: bool=False,
) -> Content:
    """
    Prepare context to update a Content. It will add a new updatable revision
    to the content.
    :param session: Database _session
    :param tm: TransactionManager
    :param content: Content instance to update
    :param force_create_new_revision: Decide if new_rev should or should not
    be forced.
    :return:
    """
    with session.no_autoflush:
        try:
            if force_create_new_revision \
                    or inspect(content.revision).has_identity:
                content.new_revision()
            RevisionsIntegrity.add_to_updatable(content.revision)
            yield content
        except SameValueError or ValueError as e:
            # INFO - 20-03-2018 - renew transaction when error happened
            # This avoid bad _session data like new "temporary" revision
            # to be add when problem happen.
            tm.abort()
            tm.begin()
            raise e
        finally:
            RevisionsIntegrity.remove_from_updatable(content.revision)