# -*- coding: utf-8 -*-
#
# OpenCraft -- tools to aid developing and hosting free software projects
# Copyright (C) 2015-2019 OpenCraft <contact@opencraft.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
"""
Instance app - Factory functions for creating instances
"""

# Imports #####################################################################

import logging
import yaml

from django.conf import settings

from instance import ansible
from instance.models.database_server import MySQLServer, MongoDBServer
from instance.models.mixins.storage import StorageContainer
from instance.models.openedx_instance import OpenEdXInstance


# Logging #####################################################################

logger = logging.getLogger(__name__)


# Functions ###################################################################

def _check_environment():
    """
    Check environment and report potential problems for production instances
    """
    if settings.INSTANCE_STORAGE_TYPE == StorageContainer.S3_STORAGE and \
            not(settings.AWS_ACCESS_KEY_ID and settings.AWS_SECRET_ACCESS_KEY):
        logger.warning(
            "AWS support is currently enabled. Add AWS_ACCESS_KEY_ID and "
            "AWS_SECRET_ACCESS_KEY settings or adjust INSTANCE_STORAGE_TYPE setting."
        )
        return False
    if not MySQLServer.objects.exists() and settings.DEFAULT_INSTANCE_MYSQL_URL is None:
        logger.warning(
            "No MySQL servers configured, and default URL for external MySQL database is missing."
            "Create at least one MySQLServer, or set DEFAULT_INSTANCE_MYSQL_URL in your .env."
        )
        return False
    if not MongoDBServer.objects.exists() and settings.DEFAULT_INSTANCE_MONGO_URL is None:
        logger.warning(
            "No MongoDB servers configured, and default URL for external MongoDB database is missing."
            "Create at least one MongoDBServer, or set DEFAULT_INSTANCE_MONGO_URL in your .env."
        )
        return False
    return True


def instance_factory(**kwargs):
    """
    Factory function for creating instances.

    Returns a newly created OpenEdXInstance.

    Callers can use keyword arguments to pass in non-default values
    for any field that is defined on the OpenEdXInstance model.

    The only mandatory argument is `sub_domain`.

    When called without any additional arguments, the instance that is returned
    will have its fields set to default values that are appropriate
    for *sandbox* instances.

    To create an instance with default settings that are suitable for production,
    use `production_instance_factory`.
    """
    # Ensure caller provided required arguments
    assert "sub_domain" in kwargs

    # Create instance
    instance = OpenEdXInstance.objects.create(**kwargs)
    return instance


def production_instance_factory(**kwargs):
    """
    Factory function for creating production instances.

    Returns a newly created OpenEdXInstance.

    Callers can use keyword arguments to pass in non-default values
    for any field that is defined on the OpenEdXInstance model.

    The only mandatory argument is `sub_domain`.

    When called without any additional arguments, the instance that is returned
    will have its fields set to default values that are appropriate
    for *production* instances.

    To create an instance with default settings that are suitable for sandboxes,
    use `instance_factory`.
    """
    # NOTE: The long-term goal is to eliminate differences between sandboxes
    # and production instances, and for this function to disappear.
    # Please do not add behavior that is specific to production instances here.

    # Ensure caller provided required arguments
    assert "sub_domain" in kwargs

    # Check environment and report potential problems
    environment_ready = _check_environment()

    if not environment_ready:
        logger.warning("Environment not ready. Please fix the problems above, then try again. Aborting.")
        return None

    # Gather settings
    production_settings = {
        # Don't create default users on production instances
        "DEMO_CREATE_STAFF_USER": False,
        "demo_test_users": [],
        # Disable certificates process to reduce load on RabbitMQ and MySQL
        "SANDBOX_ENABLE_CERTIFICATES": False,
    }
    configuration_extra_settings = kwargs.pop("configuration_extra_settings", "")
    if configuration_extra_settings:
        configuration_extra_settings = yaml.load(configuration_extra_settings, Loader=yaml.SafeLoader)
    else:
        configuration_extra_settings = {}
    extra_settings = yaml.dump(
        ansible.dict_merge(production_settings, configuration_extra_settings),
        default_flow_style=False
    )
    instance_kwargs = dict(
        edx_platform_repository_url=settings.STABLE_EDX_PLATFORM_REPO_URL,
        edx_platform_commit=settings.STABLE_EDX_PLATFORM_COMMIT,
        configuration_source_repo_url=settings.STABLE_CONFIGURATION_REPO_URL,
        configuration_version=settings.STABLE_CONFIGURATION_VERSION,
        openedx_release=settings.OPENEDX_RELEASE_STABLE_REF,
        configuration_extra_settings=extra_settings,
        # Allow production instances to use a different OpenStack instance flavor by default.
        # This allows using a larger instance flavor for production instances.
        openstack_server_flavor=settings.OPENSTACK_PRODUCTION_INSTANCE_FLAVOR,
    )
    instance_kwargs.update(kwargs)

    # Create instance
    production_instance = OpenEdXInstance.objects.create(**instance_kwargs)
    return production_instance