# vim: tabstop=4 shiftwidth=4 softtabstop=4

#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from collections import namedtuple
import copy
from datetime import timedelta
import logging
from random import randint
import re

from django.conf import settings
from django.db.models import Max
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from requests import ConnectionError

from novaclient.v1_1.contrib import baremetal
from tuskarclient.v1 import client as tuskar_client

from openstack_dashboard.api import base
from openstack_dashboard.api import nova

import tuskar_ui.infrastructure.models as dummymodels


LOG = logging.getLogger(__name__)
TUSKAR_ENDPOINT_URL = getattr(settings, 'TUSKAR_ENDPOINT_URL')
REMOTE_NOVA_BAREMETAL_CREDS = getattr(settings, 'REMOTE_NOVA_BAREMETAL_CREDS',
                                      False)
OVERCLOUD_CREDS = getattr(settings, 'OVERCLOUD_CREDS', False)


# FIXME: request isn't used right in the tuskar client right now, but looking
# at other clients, it seems like it will be in the future
def tuskarclient(request):
    c = tuskar_client.Client(TUSKAR_ENDPOINT_URL)
    return c


def baremetalclient(request):
    def create_remote_nova_client_baremetal():
        nc = nova.nova_client.Client(REMOTE_NOVA_BAREMETAL_CREDS['user'],
                        REMOTE_NOVA_BAREMETAL_CREDS['password'],
                        REMOTE_NOVA_BAREMETAL_CREDS['tenant'],
                        auth_url=REMOTE_NOVA_BAREMETAL_CREDS['auth_url'],
                        bypass_url=REMOTE_NOVA_BAREMETAL_CREDS['bypass_url'])
        return nc

    def create_nova_client_baremetal():
        insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
        nc = nova.nova_client.Client(request.user.username,
                                     request.user.token.id,
                                     project_id=request.user.tenant_id,
                                     auth_url=base.url_for(request, 'compute'),
                                     insecure=insecure,
                                     http_log_debug=settings.DEBUG)
        nc.client.auth_token = request.user.token.id
        nc.client.management_url = base.url_for(request, 'compute')

        return nc

    if REMOTE_NOVA_BAREMETAL_CREDS:
        LOG.debug('remote nova baremetal client connection created')
        nc = create_remote_nova_client_baremetal()
    else:
        LOG.debug('nova baremetal client connection created using token "%s" '
                  'and url "%s"' %
                  (request.user.token.id, base.url_for(request, 'compute')))
        nc = create_nova_client_baremetal()

    return baremetal.BareMetalNodeManager(nc)


def overcloudclient(request):
    c = nova.nova_client.Client(OVERCLOUD_CREDS['user'],
                                OVERCLOUD_CREDS['password'],
                                OVERCLOUD_CREDS['tenant'],
                                auth_url=OVERCLOUD_CREDS['auth_url'])
    return c


class StringIdAPIResourceWrapper(base.APIResourceWrapper):
    # horizon DataTable class expects ids to be string,
    # if it's not string, then comparison in
    # horizon/tables/base.py:get_object_by_id fails.
    # Because of this, ids returned from dummy api are converted to string
    # (luckily django autoconverts strings to integers when passing string to
    # django model id)

    def __init__(self, apiresource, request=None):
        self.request = request
        self._apiresource = apiresource

    # FIXME
    # this is redefined from base.APIResourceWrapper,
    # remove this when tuskarclient returns object instead of dict
    def __getattr__(self, attr):
        if attr in self._attrs:
            if issubclass(self._apiresource.__class__, dict):
                return self._apiresource.get(attr)
            else:
                return self._apiresource.__getattribute__(attr)
        else:
            raise AttributeError(attr)

    @property
    def id(self):
        return str(self._apiresource.id)

    # FIXME: self.request is required when calling some instance
    # methods (e.g. list_flavors), once we really start using this request
    # param (if ever), a proper request value should be set
    @property
    def request(self):
        return getattr(self, '_request', None)

    @request.setter
    def request(self, value):
        setattr(self, '_request', value)


class Alert(StringIdAPIResourceWrapper):
    """Wrapper for the Alert object returned by the
    dummy model.
    """
    _attrs = ['message', 'time']


class Capacity(StringIdAPIResourceWrapper):
    """Wrapper for the Capacity object returned by the
    dummy model.
    """
    _attrs = ['name', 'value', 'unit']

    @classmethod
    def create(cls, request, **kwargs):
        c = dummymodels.Capacity(
            content_object=kwargs['content_object'],
            name=kwargs['name'],
            value=kwargs['value'],
            unit=kwargs['unit'])
        c.save()
        return Capacity(c)

    @classmethod
    def update(cls, request, **kwargs):
        c = dummymodels.Capacity.objects.get(id=kwargs['capacity_id'])
        c.content_object = kwargs['content_object']
        c.name = kwargs['name']
        c.value = kwargs['value']
        c.unit = kwargs['unit']
        c.save()
     
   return cls(c)

    # defines a random usage of capacity - API should probably be able to
    # determine
 usage of capacity based on capacity value and obejct_id
    @property
    def usage(self):
        if not hasattr(self, '_usage'):
            self._usage = randint(0, int(self.value))
        return self._usage

    # defines a random average of capacity - API should probably be able to
    # determine average of capacity based on capacity value and obejct_id
    @property
    def average(self):
        if not hasattr(self, '_average'):
            self._average = randint(0, int(self.value))
        return self._average


class Node(StringIdAPIResourceWrapper):
    """Wrapper for the Node object  returned by the
    dummy model.
    """
    _attrs = ['id', 'pm_address', 'cpus', 'memory_mb', 'service_host',
              'local_gb', 'pm_user']

    @classmethod
    def get(cls, request, node_id):
        node = cls(baremetalclient(request).get(node_id))
        node.request = request

        # FIXME ugly, fix after demo, make abstraction of instance details
        # this is realy not optimal, but i dont hve time do fix it now
        instances, more = nova.server_list(
            request,
            search_opts={'paginate': True},
            all_tenants=True)

        instance_details = {}
        for instance in instances:
            id = (instance.
                  _apiresource._info['OS-EXT-SRV-ATTR:hypervisor_hostname'])
            instance_details[id] = instance

        detail = instance_details.get(node_id)
        if detail:
            addresses = detail._apiresource.addresses.get('ctlplane')
            if addresses:
                node.ip_address_other = (", "
                    .join([addr['addr'] for addr in addresses]))

            node.status = detail._apiresource._info['OS-EXT-STS:vm_state']
            node.power_management = ""
            if node.pm_user:
                node.power_management = node.pm_user + "/********"
        else:
            node.status = 'unprovisioned'

        return node

    @classmethod
    def list(cls, request):
        return [Node(n, request) for n in
                baremetalclient(request).list()]

    @classmethod
    def list_unracked(cls, request):
        try:
            return [n for n in Node.list(request) if (n.