#!/usr/bin/env python
# Copyright 2015 Mirantis, Inc.
#
# 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.
import copy
import logging
import os
import re
import requests
import types
import urlparse

import urwid
import urwid.raw_display
import urwid.web_display

from fuelmenu.common import modulehelper
from fuelmenu.common import utils

log = logging.getLogger('fuelmenu.mirrors')
blank = urwid.Divider()

localhost_pattern = re.compile(r'(127.0.0.1|localhost)')


BOOTSTRAP_HTTP_PROXY_KEY = "BOOTSTRAP/http_proxy"
BOOTSTRAP_HTTPS_PROXY_KEY = "BOOTSTRAP/https_proxy"
BOOTSTRAP_NO_PROXY_KEY = "BOOTSTRAP/no_proxy"
BOOTSTRAP_REPOS_KEY = "BOOTSTRAP/repos"
BOOTSTRAP_SKIP_BUILD_KEY = "BOOTSTRAP/skip_default_img_build"

ADD_REPO_BUTTON_KEY = 'add_repo_button'


class BootstrapImage(urwid.WidgetWrap):
    def __init__(self, parent):
        self.name = "Bootstrap Image"
        self.visible = True
        self.parent = parent

        # UI Text
        self.header_content = ["Bootstrap image configuration"]

        self._common_fields = (
            modulehelper.BLANK_KEY,
            BOOTSTRAP_SKIP_BUILD_KEY,
        )

        self._repo_related_fields = (
            modulehelper.BLANK_KEY,
            BOOTSTRAP_HTTP_PROXY_KEY,
            BOOTSTRAP_HTTPS_PROXY_KEY,
            BOOTSTRAP_NO_PROXY_KEY,
            modulehelper.BLANK_KEY,
            BOOTSTRAP_REPOS_KEY,
            ADD_REPO_BUTTON_KEY
        )

        self.fields = self._common_fields

        self.repo_list = []

        self.repo_value_scheme = {
            "name": {
                "type": modulehelper.WidgetType.TEXT_FIELD,
                "label": "Name",
                "tooltip": "Repository name"
            },
            "uri": {
                "type": modulehelper.WidgetType.TEXT_FIELD,
                "label": "Deb repo",
                "tooltip": "Repo in format: "
                           "deb uri distribution [component1] [...]"
            },
            "priority": {
                "type": modulehelper.WidgetType.TEXT_FIELD,
                "label": "Priority",
                "tooltip": "Repository priority"
            }
        }

        self.defaults = {
            BOOTSTRAP_SKIP_BUILD_KEY: {
                "label": "Skip building bootstrap image",
                "tooltip": "",
                "type": modulehelper.WidgetType.CHECKBOX,
                "callback": self.skip_build_callback},
            BOOTSTRAP_HTTP_PROXY_KEY: {
                "label": "HTTP proxy",
                "tooltip": "Use this proxy when building the bootstrap image",
                "value": ""},
            BOOTSTRAP_HTTPS_PROXY_KEY: {
                "label": "HTTPS proxy",
                "tooltip": "Use this proxy when building the bootstrap image",
                "value": ""},
            BOOTSTRAP_NO_PROXY_KEY: {
                "label": "Bypass proxy for",
                "tooltip": "Bypass proxy for domains from this list",
                "value": ""},
            BOOTSTRAP_REPOS_KEY: {
                "label": "List of repositories",
                "type": modulehelper.WidgetType.LIST,
                "value_scheme": self.repo_value_scheme,
                "value": self.repo_list
            },
            ADD_REPO_BUTTON_KEY: {
                "label": "Add repository",
                "type": modulehelper.WidgetType.BUTTON,
                "callback": self.add_repo
            }
        }
        self.load()
        self.screen = None

    @property
    def responses(self):
        ret = dict()
        for index, fieldname in enumerate(self.fields):
            if (fieldname == modulehelper.BLANK_KEY or
                    'button' in fieldname.lower()):
                pass
            elif fieldname == BOOTSTRAP_REPOS_KEY:
                ret[fieldname] = \
                    self._get_repo_list_response(self.edits[index])
            elif fieldname == BOOTSTRAP_SKIP_BUILD_KEY:
                ret[fieldname] = self.edits[index].get_state()
            else:
                ret[fieldname] = self.edits[index].get_edit_text()
        return ret

    def check(self, args):
        """Validate that all fields have valid values through sanity checks."""
        self.parent.footer.set_text("Checking data...")
        self.parent.refreshScreen()
        responses = self.responses

        if self.parent.save_only:
            return responses

        errors = []
        if not responses.get(BOOTSTRAP_SKIP_BUILD_KEY):
            errors.extend(self.check_apt_repos(responses))

        if errors:
            log.error("Errors: %s", errors)
            modulehelper.ModuleHelper.display_failed_check_dialog(self, errors)
            return False
        else:
            self.parent.footer.set_text("No errors found.")
            return responses

    def check_apt_repos(self, responses):
        errors = []

        http_proxy = responses[BOOTSTRAP_HTTP_PROXY_KEY].strip()
        https_proxy = responses[BOOTSTRAP_HTTPS_PROXY_KEY].strip()
        no_proxy = responses[BOOTSTRAP_NO_PROXY_KEY].strip()

        # Set up proxy settings
        os.environ['HTTP_PROXY'] = http_proxy
        os.environ['HTTPS_PROXY'] = https_proxy
        os.environ['NO_PROXY'] = no_proxy

        repos = responses.get(BOOTSTRAP_REPOS_KEY)

        if not repos:
            errors.append("Specify at least one repository.")

        for index, repo in enumerate(repos):
            name = repo['name']
            priority = repo['priority']
            if priority and not isinstance(priority,
                                           (types.IntType, types.NoneType)):
                errors.append("Priority value for repository {0} should be "
                              "empty or numeric.".format(name))
            if not name:
                name = "#{0}".format(index + 1)
                errors.append("Empty name for repository {0}."
                              .format(name))
            if not all((repo['type'], repo['uri'], repo['suite'])):
                errors.append("Cannot parse repository {0}. "
                              "Expected format: "
                              "'deb uri distribution [component1] [...]'."
                              .format(name))
                continue
            if not self._check_repo(repo['uri'], repo['suite']):
                errors.append("URL for repository {0} is not accessible."
                              .format(name))

        return errors

    def apply(self, args):
        responses = self.check(args)
        if responses is False:
            log.error("Check failed. Not applying")
            log.error("%s" % (responses))
            return False
        self.save(responses)
        return True

    def cancel(self, button):
        modulehelper.ModuleHelper.cancel(self, button)

    def _get_repo_list_response(self, list_box):
        # Here we assumed that we get object of WalkerStoredListBox
        # which contains link for list_walker.
        if not hasattr(list_box, 'list_walker'):
            return []
        external_lw = list_box.list_walker

        # on UI we have labels, but not keys...
        label_to_key_mapping = dict((v['label'], k) for k, v in
                                    self.repo_value_scheme.items())
        result = []
        for lb in external_lw:
            repo = {}
            internal_lw = getattr(lb, 'list_walker', None)
            if not internal_lw:
                continue

            for edit in internal_lw:
                if not hasattr(edit, 'caption'):
                    continue
                key = label_to_key_mapping[edit.caption.strip()]
                repo[key] = edit.edit_text

            if any(repo.values()):  # skip empty entries
                result.append(self._parse_ui_repo_entry(repo))
        return result

    def _parse_ui_repo_entry(self, repo_from_ui):
        regexp = r"(?P<type>\w+) (?P<uri>[^\s]+) (?P<suite>[^\s]+)( " \
                 r"(?P<section>[\w\s]*))?"

        priority = repo_from_ui.get('priority')
        name = repo_from_ui.get('name')
        uri = repo_from_ui.get('uri', '')
        try:
            priority = int(priority) if priority else None
        except (TypeError, ValueError):
            log.debug("Wrong priority value for repository '{0}': "
                      "'{1}'.".format(name, priority))

        match = re.match(regexp, uri)

        repo_type = match.group('type') if match else ''
        repo_suite = match.group('suite') if match else ''
        repo_section = match.group('section') if match else ''
        repo_uri = match.group('uri') if match else uri

        return {
            "name": name,
            "type": repo_type,
            "uri": repo_uri,
            "priority": priority,
            "suite": repo_suite,
            "section": repo_section
        }

    def _parse_config_repo_entry(self, repo_from_config):
        uri_template = "{type} {uri} {suite}"
        section_suffix = " {section}"

        section = repo_from_config.get('section')
        name = repo_from_config.get('name', '')
        priority = repo_from_config.get('priority', '')
        if not priority:
            # we can get None from config
            priority = ""

        data = {
            "suite": repo_from_config.get('suite', ''),
            "type": repo_from_config.get('type', ''),
            "uri": repo_from_config.get('uri', '')
        }

        if section:
            data["section"] = section
            uri_template += section_suffix
        uri = ''
        if any(data.values()):
            uri = uri_template.format(**data).strip()
        result = {
            "uri": uri,
            "name": name,
            "priority": str(priority)
        }

        return result

    def _parse_config_repo_list(self, repos_from_config):
        # There are two different formats for repositories in config and UI:
        # on UI we have 3 fields: Name, URI and Priority,
        # where 'URI' actually contains repo type, suite and section. Example:
        # deb http://archive.ubuntu.com/ubuntu/ trusty main universe multiverse
        # In config file 'uri', 'type', 'suite' and 'section' are stored
        # separate. So we need to convert this list from one format to another.

        repos_for_ui = []
        for entry in repos_from_config:
            repos_for_ui.append(self._parse_config_repo_entry(entry))
        return repos_for_ui

    def add_repo(self, data=None):
        defaults = self._get_fresh_defaults()
        repo_list = defaults[BOOTSTRAP_REPOS_KEY]['value']
        repo_list.append(
            dict((k, "") for k in self.repo_value_scheme))
        button_position = self._calculate_field_position(ADD_REPO_BUTTON_KEY)
        self._redraw_screen(defaults, button_position)

    def _update_defaults(self, defaults, new_settings):
        for setting in defaults:
            try:
                new_value = modulehelper.ModuleHelper.get_setting(
                    new_settings, setting)
                if BOOTSTRAP_REPOS_KEY == setting:
                    defaults[setting]["value"] = \
                        self._parse_config_repo_list(new_value)
                    continue

                defaults[setting]["value"] = new_value
            except KeyError:
                log.warning("no setting named {0} found.".format(setting))
            except Exception as e:
                log.warning("unexpected error: {0}".format(e))

    def load(self):
        settings = self.parent.settings
        modulehelper.ModuleHelper.load_to_defaults(settings, self.defaults)

        self._update_defaults(self.defaults, settings)
        self._select_fields_to_show(self.defaults)

    def save(self, responses):
        newsettings = modulehelper.ModuleHelper.make_settings_from_responses(
            responses)
        self.parent.settings.merge(newsettings)

        # Update self.defaults
        self._update_defaults(self.defaults, self.parent.settings)

    def check_url(self, url):
        try:
            resp = requests.get(url, verify=False)
        except (requests.exceptions.RequestException,
                requests.exceptions.BaseHTTPError) as e:
            log.error(e)
            return False
        return resp.ok

    def _check_repo(self, base_url, suite):
        release_url = '{base_url}/dists/{suite}/Release'.format(
            base_url=base_url, suite=suite)
        host = urlparse.urlparse(release_url).netloc.split(':')[0]
        if (localhost_pattern.search(host) and
                utils.is_pre_deployment()):
            # Due to pre-deployment stage we can't check accessibility of local
            # repository since it is not created at that moment. Although we
            # still should provide an ability to use it, because in fact
            # it will be created at the end of deployment.
            log.warning('Accessibility check is skipped for local '
                        'repository: {}'.format(release_url))
            return True

        return self.check_url(release_url)

    def refresh(self):
        pass

    def _generate_screen_by_defaults(self, defaults):
        screen = modulehelper.ModuleHelper.screenUI(self, self.header_content,
                                                    self.fields, defaults)
        return screen

    def _get_fresh_defaults(self):
        defaults = copy.copy(self.defaults)
        self._update_defaults(
            defaults,
            modulehelper.ModuleHelper.make_settings_from_responses(
                self.responses))
        return defaults

    def _redraw_screen(self, defaults=None, focus_position=None):
        if not defaults:
            defaults = self.defaults
        self.screen = self._generate_screen_by_defaults(defaults)
        if focus_position:
            self.walker.set_focus(focus_position)
        self.parent.draw_child_screen(self.screen, focus_on_child=True)

    def _calculate_field_position(self, field_name):
        return self._calculate_edits_offset() + self.fields.index(field_name)

    def _calculate_edits_offset(self):
        """Returns position of first widget."""
        first_edit = self.edits[0] if self.edits else None
        result = 0
        for widget in self.walker.lst:
            if widget == first_edit:
                break
            result += 1
        return result

    def _select_fields_to_show(self, defaults):
        skip_build = defaults[BOOTSTRAP_SKIP_BUILD_KEY].get('value')
        if skip_build:
            self.fields = self._common_fields
            return
        self.fields = \
            self._common_fields +\
            self._repo_related_fields

    def skip_build_callback(self, widget, new_state):
        defaults = self._get_fresh_defaults()
        defaults[BOOTSTRAP_SKIP_BUILD_KEY]['value'] = new_state
        self._select_fields_to_show(defaults)
        self._redraw_screen(
            defaults,
            self._calculate_field_position(BOOTSTRAP_SKIP_BUILD_KEY))

    def screenUI(self):
        return self._generate_screen_by_defaults(self.defaults)
