# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import re
import datetime
import calendar

from django.contrib import messages
from django.db import models as db
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import get_object_or_404
from django.utils import simplejson as json

from bob.menu import MenuItem

from ralph.account.models import Perm
from ralph.business.models import Venture, VentureRole, VentureExtraCost
from ralph.discovery.models import (
    ComponentModel,
    DataCenter,
    Device,
    DeviceModelGroup,
    DeviceType,
    HistoryCost,
    ReadOnlyDevice,
    SplunkUsage,
)
from ralph.ui.forms import (
    DateRangeForm,
    RolePropertyForm,
    VentureFilterForm,
)
from ralph.ui.views.common import (
    Addresses,
    Asset,
    Base,
    BaseMixin,
    Components,
    Costs,
    History,
    Info,
    Prices,
    Software,
    Scan,
)
from ralph.ui.views.devices import BaseDeviceList
from ralph.ui.views.reports import Reports, ReportDeviceList
from ralph.ui.reports import (
    get_total_cost,
    get_total_count,
    get_total_cores,
    get_total_virtual_cores,
)
from ralph.util import presentation


def _normalize_venture(symbol):
    """
    >>> _normalize_venture('węgielek,Ziew')
    u'w.gielek.ziew'
    """
    return re.sub(r'[^\w]', '.', symbol).lower()


def collect_ventures(parent, ventures, items, depth=0):
    for v in ventures.filter(parent=parent):
        symbol = _normalize_venture(v.symbol)
        indent = ' ' * depth
        icon = presentation.get_venture_icon(v)
        if icon == 'fugue-store':
            if depth > 0:
                icon = 'fugue-store-medium'
            if depth > 1:
                icon = 'fugue-store-small'
        items.append((icon, v.name, symbol, indent, v))
        collect_ventures(v, ventures, items, depth + 1)


def venture_tree_menu(ventures, details, show_all=False):
    items = []
    if not show_all:
        ventures = ventures.filter(show_in_ralph=True)
    for v in ventures.order_by('-is_infrastructure', 'name'):
        symbol = _normalize_venture(v.symbol)
        icon = presentation.get_venture_icon(v)
        item = MenuItem(
            v.name, name=symbol,
            fugue_icon=icon,
            view_name='ventures',
            view_args=[symbol, details, ''],
            indent=' ',
            collapsed=True,
            collapsible=True,
        )
        item.venture_id = v.id
        item.subitems = venture_tree_menu(
            v.child_set.all(), details, show_all)
        for subitem in item.subitems:
            subitem.parent = item
        items.append(item)
    return items


class SidebarVentures(object):
    section = 'ventures'

    def __init__(self, *args, **kwargs):
        super(SidebarVentures, self).__init__(*args, **kwargs)
        self.venture = None

    def set_venture(self):
        if self.venture is not None:
            return
        venture_symbol = self.kwargs.get('venture')
        if venture_symbol in ('', '-'):
            self.venture = ''
        elif venture_symbol == '*':
            self.venture = '*'
        elif venture_symbol:
            self.venture = get_object_or_404(Venture,
                                             symbol__iexact=venture_symbol)
        else:
            self.venture = None

    def get_context_data(self, **kwargs):
        ret = super(SidebarVentures, self).get_context_data(**kwargs)
        self.set_venture()
        details = ret['details']
        profile = self.request.user.get_profile()
        has_perm = profile.has_perm
        ventures = profile.perm_ventures(Perm.list_devices_generic)
        show_all = self.request.GET.get('show_all')
        ventures = ventures.order_by('-is_infrastructure', 'name')

        sidebar_items = [
            MenuItem(fugue_icon='fugue-prohibition', label="Unknown",
                     name='-', view_name='ventures',
                     view_args=['-', details, '']),
            MenuItem(fugue_icon='fugue-asterisk', label="All ventures",
                     name='*', view_name='ventures',
                     view_args=['*', details, ''])
        ]
        sidebar_items.extend(venture_tree_menu(
            ventures.filter(parent=None), details, show_all))
        if self.venture and self.venture != '*':
            stack = list(sidebar_items)
            while stack:
                item = stack.pop()
                if getattr(item, 'venture_id', None) == self.venture.id:
                    parent = getattr(item, 'parent', None)
                    while parent:
                        parent.kwargs['collapsed'] = False
                        parent = getattr(parent, 'parent', None)
                    break
                stack.extend(getattr(item, 'subitems', []))

        self.set_venture()
        tab_items = ret['tab_items']
        if has_perm(Perm.read_device_info_generic, self.venture if
                    self.venture and self.venture != '*' else None):
            tab_items.append(MenuItem('Roles', fugue_icon='fugue-mask',
                                      href='../roles/?%s' % self.request.GET.urlencode()))
        if has_perm(Perm.list_devices_financial, self.venture if
                    self.venture and self.venture != '*' else None):
            tab_items.append(MenuItem('Venture', fugue_icon='fugue-store',
                                      href='../venture/?%s' %
                                      self.request.GET.urlencode()))
        ret.update({
            'sidebar_items': sidebar_items,
            'sidebar_selected': (_normalize_venture(self.venture.symbol) if
                                 self.venture and self.venture != '*' else self.venture or '-'),
            'section': 'ventures',
            'subsection': (_normalize_venture(self.venture.symbol) if
                           self.venture and self.venture != '*' else self.venture),
            'searchform': VentureFilterForm(self.request.GET),
            'searchform_filter': True,
            'show_bulk': True if self.venture else False,
        })
        return ret


class Ventures(SidebarVentures, BaseMixin):
    pass


class VenturesInfo(Ventures, Info):
    pass


class VenturesComponents(Ventures, Components):
    pass


class VenturesSoftware(Ventures, Software):
    pass


class VenturesPrices(Ventures, Prices):
    pass


class VenturesAddresses(Ventures, Addresses):
    pass


class VenturesCosts(Ventures, Costs):
    pass


class VenturesHistory(Ventures, History):
    pass


class VenturesAsset(Ventures, Asset):
    pass


class VenturesReports(Ventures, Reports):
    pass


class VenturesScan(Ventures, Scan):
    pass


class VenturesRoles(Ventures, Base):
    template_name = 'ui/ventures-roles.html'

    def __init__(self, *args, **kwargs):
        super(VenturesRoles, self).__init__(*args, **kwargs)
        self.form = None

    def post(self, *args, **kwargs):
        self.set_venture()
        has_perm = self.request.user.get_profile().has_perm
        if not has_perm(Perm.edit_ventures_roles, self.venture if
                        self.venture and self.venture != '*' else None):
            messages.error(self.request, "No permission to edit that role.")
        else:
            self.form = RolePropertyForm(self.request.POST)
            if self.form.is_valid():
                self.form.save()
                messages.success(self.request, "Property created.")
                return HttpResponseRedirect(self.request.path)
            else:
                messages.error(self.request, "Correct the errors.")
        return self.get(*args, **kwargs)

    def get(self, *args, **kwargs):
        self.set_venture()
        role_id = self.kwargs.get('role')
        if role_id:
            self.role = get_object_or_404(VentureRole, id=role_id)
        else:
            self.role = None
        if self.form is None:
            if self.role:
                self.form = RolePropertyForm(initial={'role': role_id})
            else:
                self.form = RolePropertyForm(
                    initial={'venture': self.venture.id})
        return super(VenturesRoles, self).get(*args, **kwargs)

    def get_context_data(self, **kwargs):
        ret = super(VenturesRoles, self).get_context_data(**kwargs)
        has_perm = self.request.user.get_profile().has_perm
        ret.update({
            'items': (self.venture.venturerole_set.all() if
                      self.venture and self.venture != '*' else []),
            'role': self.role,
            'venture': self.venture,
            'form': self.form,
            'editable': has_perm(Perm.edit_ventures_roles, self.venture if
                                 self.venture and self.venture != '*' else None),
        })
        return ret


def _total_dict(name, query, start, end, url=None):
    cost = get_total_cost(query, start, end)
    count, count_now, devices = get_total_count(query, start, end)
    if not count and not count_now:
        return None
    return {
        'name': name,
        'count': count,
        'cost': cost,
        'count_now': count_now,
        'url': url,
    }


def _get_search_url(venture, dc=None, type=(), model_group=None):
    if venture == '':
        venture_id = '-'
    elif venture == '*':
        venture_id = '*'
    elif venture is None:
        venture_id = ''
    else:
        venture_id = venture.id
    params = [
        ('role', venture_id),
    ]
    for t in type:
        params.append(('device_type', '%d' % t))
    if model_group:
        params.append(('device_group', '%d' % model_group))
    if dc:
        params.append(('position', dc.name))
    return '/ui/search/info/?%s' % '&'.join('%s=%s' % p for p in params)


def _get_summaries(query, start, end, overlap=True, venture=None):
    if overlap:
        yield _total_dict('Servers', query.filter(
            device__model__type__in=(DeviceType.rack_server.id,
                                     DeviceType.blade_server.id,
                                     DeviceType.virtual_server.id)), start, end,
                          _get_search_url(venture, type=(201, 202, 203)))
    for dc in DataCenter.objects.all():
        yield _total_dict('  • Servers in %s' % dc.name, query.filter(
            device__model__type__in=(DeviceType.rack_server.id,
                                     DeviceType.blade_server.id,
                                     DeviceType.virtual_server.id)
        ).filter(device__dc__iexact=dc.name), start, end,
            _get_search_url(venture, dc=dc, type=(201, 202, 203))
        )
        if overlap:
            yield _total_dict(
                '    ∙ Rack servers in %s' % dc.name, query.filter(
                    device__model__type=DeviceType.rack_server.id,
                ).filter(device__dc__iexact=dc.name), start, end,
                _get_search_url(venture, dc=dc, type=(201,))
            )
            for mg in DeviceModelGroup.objects.filter(
                    type=DeviceType.rack_server.id).order_by('name'):
                yield _total_dict(
                    '        %s in %s' % (mg, dc.name), query.filter(
                        device__model__group=mg,
                    ).filter(device__dc__iexact=dc.name), start, end,
                    _get_search_url(venture, dc=dc, type=(201,),
                                    model_group=mg.id)
                )
            yield _total_dict(
                '    ∙ Blade servers in %s' % dc.name, query.filter(
                    device__model__type=DeviceType.blade_server.id,
                ).filter(device__dc__iexact=dc.name), start, end,
                _get_search_url(venture, dc=dc, type=(202,))
            )
            for mg in DeviceModelGroup.objects.filter(
                    type=DeviceType.blade_server.id).order_by('name'):
                yield _total_dict(
                    '        %s in %s' % (mg, dc.name), query.filter(
                        device__model__group=mg,
                    ).filter(device__dc__iexact=dc.name), start, end,
                    _get_search_url(venture, dc=dc, type=(202,),
                                    model_group=mg.id)
                )
            yield _total_dict(
                '    ∙ Virtual servers in %s' % dc.name, query.filter(
                    device__model__type=DeviceType.virtual_server.id,
                ).filter(device__dc__iexact=dc.name), start, end,
                _get_search_url(venture, dc=dc, type=(203,))
            )
    if overlap:
        yield _total_dict('Loadbalancers', query.filter(
            device__model__type__in=(DeviceType.load_balancer.id,)
        ), start, end, _get_search_url(venture, type=(103,)))
    for dc in DataCenter.objects.all():
        yield _total_dict(' • Loadbalancers in %s' % dc.name, query.filter(
            device__model__type__in=(DeviceType.load_balancer.id,)
        ).filter(device__dc__iexact=dc.name), start, end,
            _get_search_url(venture, dc=dc, type=(103,))
        )
    if overlap:
        yield _total_dict('Storage', query.filter(
            device__model__type__in=(
                DeviceType.storage.id,
                DeviceType.fibre_channel_switch.id,
            )), start, end,
            _get_search_url(venture, type=(301,))
        )
    for dc in DataCenter.objects.all():
        yield _total_dict(' • Storage in %s' % dc.name, query.filter(
            device__model__type__in=(
                DeviceType.storage.id,
                DeviceType.fibre_channel_switch.id,
            )
        ).filter(device__dc__iexact=dc.name), start, end,
            _get_search_url(venture, dc=dc, type=(301,))
        )
    if overlap:
        yield _total_dict('Network', query.filter(
            device__model__type__in=(
                DeviceType.switch.id,
                DeviceType.router.id,
                DeviceType.firewall.id,
                DeviceType.smtp_gateway.id,
                DeviceType.appliance.id,
            )
        ), start, end,
            _get_search_url(venture, type=(
                DeviceType.switch.id,
                DeviceType.router.id,
                DeviceType.firewall.id,
                DeviceType.smtp_gateway.id,
                DeviceType.appliance.id,
            ))
        )
    for dc in DataCenter.objects.all():
        yield _total_dict(' • Network in %s' % dc.name, query.filter(
            device__model__type__in=(
                DeviceType.switch.id,
                DeviceType.router.id,
                DeviceType.firewall.id,
                DeviceType.smtp_gateway.id,
                DeviceType.appliance.id,
            )
        ).filter(device__dc__iexact=dc.name), start, end,
            _get_search_url(venture, dc=dc, type=(
                DeviceType.switch.id,
                DeviceType.router.id,
                DeviceType.firewall.id,
                DeviceType.smtp_gateway.id,
                DeviceType.appliance.id,
            ))
        )
    yield _total_dict('Cloud', query.filter(
        device__model__type__in=(DeviceType.cloud_server.id,)
    ), start, end,
        _get_search_url(venture, type=(DeviceType.cloud_server.id,))
    )
    if overlap:
        yield _total_dict('Unknown', query.filter(
            device__model__type__in=(DeviceType.unknown.id,)), start, end,
            _get_search_url(venture, type=(DeviceType.unknown.id,))
        )
    for dc in DataCenter.objects.all():
        yield _total_dict(' • Unknown in %s' % dc.name, query.filter(
            device__model__type__in=(DeviceType.unknown.id,)
        ).filter(device__dc__iexact=dc.name), start, end,
            _get_search_url(venture, dc=dc, type=(DeviceType.unknown.id,))
        )
    (
        splunk_cost,
        splunk_count,
        splunk_count_now,
        splunk_size,
    ) = SplunkUsage.get_cost(venture, start, end)
    if splunk_cost:
        url = None
        try:
            splunk_model = ComponentModel.objects.get(family='splunkvolume')
        except ComponentModel.DoesNotExist:
            pass
        else:
            if splunk_model.group_id:
                url = ('/ui/search/components/'
                       '?component_group=%d' % splunk_model.group_id)
        yield {
            'name': 'Splunk usage ({:,.0f} MB)'.format(
                splunk_size).replace(',', ' '),
            'cost': splunk_cost,
            'count': splunk_count,
            'count_now': splunk_count_now,
            'url': url,
        }
    for extra_id, in query.values_list('extra_id').distinct():
        if extra_id is None:
            continue
        extra = VentureExtraCost.objects.get(id=extra_id)
        q = query.filter(extra=extra)
        cost = get_total_cost(q, start, end)
        count, count_now, devices = get_total_count(q, start, end)
        if count:
            yield {
                'name': extra.name + ' (from %s)' % extra.venture.name,
                'count': 'expires %s' % extra.expire.strftime(
                    '%Y-%m-%d') if extra.expire else '',
                'cost': cost,
                'count_now': count_now,
            }
    if overlap:
        yield _total_dict(
            'Total',
            query,
            start,
            end,
            _get_search_url(venture, type=()),
        )
        yield _total_dict(
            'Total physical',
            query.exclude(
                device__model__type__in=(
                    DeviceType.cloud_server,
                    DeviceType.virtual_server,
                    DeviceType.unknown,
                    DeviceType.data_center,
                    DeviceType.rack,
                    DeviceType.management,
                ),
            ).exclude(
                device=None,
            ),
            start,
            end,
            _get_search_url(venture, type=()),
        )


def _venture_children(venture, children):
    children.append(venture)
    for child in venture.child_set.all():
        _venture_children(child, children)


class VenturesVenture(SidebarVentures, Base):
    template_name = 'ui/ventures-venture.html'

    def get(self, *args, **kwargs):
        if 'start' in self.request.GET:
            self.form = DateRangeForm(self.request.GET)
            if not self.form.is_valid():
                messages.error(self.request, "Invalid date range")
        else:
            initial = {
                'start': datetime.date.today() - datetime.timedelta(days=30),
                'end': datetime.date.today(),
            }
            self.form = DateRangeForm(initial)
            self.form.is_valid()
        self.set_venture()
        has_perm = self.request.user.get_profile().has_perm
        if not has_perm(Perm.list_devices_financial, self.venture if
                        self.venture and self.venture != '*' else None):
            return HttpResponseForbidden(
                "You don't have permission to see this.")
        return super(VenturesVenture, self).get(*args, **kwargs)

    def get_context_data(self, **kwargs):
        ret = super(VenturesVenture, self).get_context_data(**kwargs)
        start = None
        end = None
        if self.venture is None or not self.form.is_valid():
            items = []
            cost_data = []
            count_data = []
            cores_data = []
            vcores_data = []
        else:
            if self.venture == '':
                query = HistoryCost.objects.filter(venture=None)
            elif self.venture == '*':
                query = HistoryCost.objects.exclude(venture=None)
            else:
                ventures = []
                _venture_children(self.venture, ventures)
                query = HistoryCost.objects.filter(
                    venture__in=ventures
                )
            start = self.form.cleaned_data['start']
            end = self.form.cleaned_data['end']
            query = query.exclude(device__deleted=True)
            items = _get_summaries(query.all(), start, end, True, self.venture)
            cost_data = []
            count_data = []
            cores_data = []
            vcores_data = []
            one_day = datetime.timedelta(days=1)
            datapoints = set(dp for dp, in
                             query.values_list('start').distinct())
            datapoints |= set(dp for dp, in
                              query.values_list('end').distinct())
            datapoints |= set([start, end])
            datapoints = set(min(max(start, date or start), end) for
                             date in datapoints)
            for date in sorted(datapoints):
                timestamp = calendar.timegm(date.timetuple()) * 1000
                total_cost = get_total_cost(query, date, date + one_day)
                total_count, now_count, devices = get_total_count(
                    query, date, date + one_day)
                total_cores = get_total_cores(query, date, date + one_day)
                total_vcores = get_total_virtual_cores(
                    query, date, date + one_day)
                cost_data.append([timestamp, total_cost])
                count_data.append([timestamp, total_count])
                cores_data.append([timestamp, total_cores])
                vcores_data.append([timestamp, total_vcores])
        ret.update({
            'items': items,
            'venture': self.venture,
            'cost_data': json.dumps(cost_data),
            'count_data': json.dumps(count_data),
            'cores_data': json.dumps(cores_data),
            'vcores_data': json.dumps(vcores_data),
            'form': self.form,
            'start_date': start,
            'end_date': end,
        })
        return ret


class VenturesDeviceList(SidebarVentures, BaseMixin, BaseDeviceList):

    def user_allowed(self):
        self.set_venture()
        has_perm = self.request.user.get_profile().has_perm
        return has_perm(Perm.list_devices_generic, self.venture if
                        self.venture and self.venture != '*' else None)

    def get_queryset(self):
        if self.venture is None:
            queryset = ReadOnlyDevice.objects.none()
        elif self.venture == '*':
            queryset = Device.objects.all()
        elif self.venture == '':
            queryset = ReadOnlyDevice.objects.filter(
                venture=None
            ).select_related(depth=3)
        else:
            queryset = ReadOnlyDevice.objects.filter(
                db.Q(venture=self.venture) |
                db.Q(venture__parent=self.venture) |
                db.Q(venture__parent__parent=self.venture) |
                db.Q(venture__parent__parent__parent=self.venture) |
                db.Q(venture__parent__parent__parent__parent=self.venture) |
                db.Q(
                    venture__parent__parent__parent__parent__parent=self.venture
                )
            ).select_related(depth=3)
        return self.sort_queryset(queryset)

    def get_context_data(self, **kwargs):
        ret = super(VenturesDeviceList, self).get_context_data(**kwargs)
        ret.update({
            'subsection': (self.venture.name if
                           self.venture and self.venture != '*' else self.venture),
            'subsection_slug': (_normalize_venture(self.venture.symbol) if
                                self.venture and self.venture != '*' else self.venture),
        })
        return ret


class ReportVenturesDeviceList(ReportDeviceList, VenturesDeviceList):
    pass
