"""
Database code for handling the retrieval and storage of journal entries.

:copyright: Copyright 2015, Dorian Pula <dorian.pula@amber-penguin-software.ca>
:license: AGPL v3+
"""

import abc
import logging

import pycouchdb
from pycouchdb import exceptions


logger = logging.getLogger(__name__)

COUCHDB_SERVER_CONNECTION = 'COUCHDB_DATABASE_URI'
COUCHDB_DATABASE = 'COUCHDB_DATABASE'


class AppError(Exception):
    pass


class InvalidModelTypeError(Exception):
    def __init__(self, expected_model, actual_model):
        self.expected = expected_model
        self.actual = actual_model
        error_message = 'Expected "{}" model, received "{}" instead.'.format(self.expected, self.actual)
        super(InvalidModelTypeError, self).__init__(error_message)


class CouchDbBackedModel(object):
    """Base class for CouchDB backed models to handle basic CouchDB things."""
    def __init__(self, doc_id, revision=None):
        self.doc_id = doc_id
        self.revision = revision


class DataStore(object):
    DATA_STORE_TYPE = "base"

    def __init__(self, app_config):
        self.database = create_or_get_database(app_config)

    def __getitem__(self, item):
        try:
            document = self.database.get(item)
        except exceptions.NotFound:
            raise KeyError
        self.assert_document_belongs_to_expected_data_type(document)
        return self.map_to_domain(document)

    def __setitem__(self, key, value):
        document = self.map_to_document(key, value)
        self.assert_document_belongs_to_expected_data_type(document)
        try:
            self.database.save(document)
        except exceptions.Error as err:
            error_message = 'Unable to save document "{doc_id}" because of "{error}"'.format(doc_id=key, error=err)
            logger.error(error_message)
            raise AppError(error_message)

    @abc.abstractmethod
    def map_to_domain(self, document):
        pass

    @abc.abstractmethod
    def map_to_document(self, document_id, domain):
        pass

    def assert_document_belongs_to_expected_data_type(self, document):
        document_type = document.get('doc_type', 'base')
        if document_type == 'base':
            raise ValueError('Document should a different type than "base".')

        if document_type != self.DATA_STORE_TYPE:
            raise InvalidModelTypeError(self.DATA_STORE_TYPE, document['doc_type'])


def create_or_get_database(config):
    server_connection = config[COUCHDB_SERVER_CONNECTION]
    couchdb_server = pycouchdb.Server(server_connection)
    database_name = config[COUCHDB_DATABASE]

    try:
        couchdb_database = couchdb_server.database(database_name)
    except exceptions.NotFound:
        logger.warn('Database "%s" not found!  Creating database.', database_name)
        couchdb_database = couchdb_server.create(database_name)
        logger.info('Database "%s" created.', database_name)
    return couchdb_database
