"""
Support for CouchDB 2.x.

:copyright: Copyright 2013-2017, Dorian Puła <dorian.pula@amber-penguin-software.ca>
:license: AGPL v3+
"""

import enum
import http
import logging
import os
from urllib import parse
import uuid

import requests
import termcolor

EXTERNAL_DB_URI_PARAM = f'ROOKERIES_COUCHDB'
DEFAULT_DB_CONNECTION = 'http://admin:password@localhost:5984/rookeries'
logger = logging.getLogger(__name__)


class OpStatus(enum.Enum):
    SKIP = '[SKIP]', 'yellow'
    OK = '[ OK ]', 'green'
    FAILED = '[FAIL]', 'red'

    def __init__(self, output, color):
        self.output = output
        self.color = color

    def print(self):
        print(termcolor.colored(self.output, self.color))


class CouchDB(object):
    """
    Manages CouchDB connections and general operations.
    """

    def __init__(self, connection=None):

        if not connection:
            connection = DEFAULT_DB_CONNECTION

        # TODO: Create a validate connection string function.
        # TODO: Add tests.
        self.url = parse.urlparse(connection)
        self.protocol = f'{self.url.scheme}://'
        self.server = f'{self.url.hostname}:{self.url.port}'
        self.credentials = f'{self.url.username}:{self.url.password}'
        self.database = self.url.path.split('/')[0]

    @staticmethod
    def configure_from_env():
        connection = os.environ.get(EXTERNAL_DB_URI_PARAM)
        return CouchDB(connection)

    def server_url(self):
        return f'{self.protocol}{self.credentials}@{self.server}'

    def connection(self):
        return self.url.geturl()

    def exists_db(self, database_name):
        db_exists = requests.get(f'{self.server_url()}/{database_name}')
        return db_exists.status_code == http.HTTPStatus.OK

    def create_db(self, database_name):
        # TODO: Add logging if operation failed!
        db_creation = requests.put(f'{self.server_url()}/{database_name}')
        return db_creation.status_code == http.HTTPStatus.CREATED

    def find_all_documents(self, selector):
        response = requests.post(
            url=f'{self.connection()}/_find/',
            json={'selector': selector}
        )

        if response.status_code != http.HTTPStatus.OK:
            logger.error(f'Error searching for "{selector}": [{response.status_code}] {response.json()}')
            response.raise_for_status()

        doc_json = response.json()
        if doc_json.get('warning'):
            logger.warning(f'CouchDB warning: {doc_json["warning"]}')

        return doc_json['docs']

    def find_first_document(self, selector):
        documents = self.find_all_documents(selector)
        return None if len(documents) == 0 else documents[0]

    def create_document(self, doc, doc_id=None):
        if not doc_id:
            doc_id = uuid.uuid4()

        # TODO: Add support for handling case of existing docs.
        return requests.put(
            url=f'{self.connection()}/{doc_id}',
            json=doc
        )


# TODO: Test and document properly.
class DatabaseModel(object):
    def __init__(self, **kwargs):
        self._id = kwargs.get('_id')
        self._rev = kwargs.get('_rev')

    @staticmethod
    def doc_type():
        raise NotImplementedError

    def to_api_json(self):
        raise NotImplementedError

    def to_db_json(self):
        raise NotImplementedError

    def attach_db_metadata(self, db_json):
        if self._id:
            db_json['_id'] = self._id
        if self._rev:
            db_json['_rev'] = self._rev
        db_json['type'] = self.doc_type()
        return db_json
