# Copyright 2019-2020 SURF.
# 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.abc import Callable
from functools import partial, wraps
from pathlib import Path
from typing import Any, Optional

import structlog
from jinja2 import Environment

from orchestrator.cli.generator.generator.helpers import (
    get_product_file_name,
    get_workflow,
    product_types_module,
    root_product_block,
)
from orchestrator.cli.generator.generator.settings import product_generator_settings
from orchestrator.cli.generator.generator.translations import add_workflow_translations
from orchestrator.cli.generator.generator.validations import get_validations, get_validations_for_modify

logger = structlog.getLogger(__name__)


def find_field_with_name(config: dict, field_name: str) -> dict:
    product_blocks = config.get("product_blocks", [])
    for pb in product_blocks:
        for field in pb.get("fields", []):
            if field["name"] == field_name:
                return {f"{field_name}_path": pb["name"]}
    return {}


def add_optional_ims_config(config: dict) -> dict:
    if found := find_field_with_name(config, "ims_circuit_id"):
        return config | found
    else:
        return config


def add_optional_nso_config(config: dict) -> dict:
    if found := find_field_with_name(config, "nso_service_id"):
        return config | found
    else:
        return config


def insert_lazy_workflow_instances(environment: Environment, config: dict, writer: Callable) -> None:
    template = environment.get_template("lazy_workflow_instance.j2")
    content = template.render(product=config)

    path = f"{product_generator_settings.WORKFLOWS_PATH}/__init__.py"
    writer(path, content, append=True)


def generate_workflows(context: dict) -> None:
    config = context["config"]
    environment = context["environment"]
    writer = context["writer"]

    create_workflow_paths(config)

    config = add_optional_nso_config(config)
    config = add_optional_ims_config(config)

    generate_shared_workflow_files(environment, config, writer)
    generate_create_workflow(environment, config, writer)
    generate_modify_workflow(environment, config, writer)
    generate_validate_workflow(environment, config, writer)
    generate_terminate_workflow(environment, config, writer)

    add_workflow_translations(config, writer)

    # insert_lazy_workflow_instances(environment, config, writer)


def workflow_folder(config: dict) -> str:
    folder = get_product_file_name(config)
    return f"{product_generator_settings.WORKFLOWS_PATH}/{folder}"


def shared_workflow_folder(config: dict) -> str:
    return f"{workflow_folder(config)}/shared"


def create_workflow_paths(config: dict) -> None:
    path = f"{workflow_folder(config)}/shared"
    Path(path).mkdir(parents=True, exist_ok=True)


def get_workflow_path(config: dict, workflow_type: str) -> str:
    file_name = get_product_file_name(config)
    return f"{workflow_folder(config)}/{workflow_type}_{file_name}.py"


def generate_shared_workflow_files(environment: Environment, config: dict, writer: Callable) -> None:
    product_block = root_product_block(config)
    validations, _ = get_validations(config)

    template = environment.get_template("shared_forms.j2")
    content = template.render(product=config, product_block=product_block, validations=validations)

    path = f"{shared_workflow_folder(config)}/forms.py"

    writer(path, content)


def generate_workflow(f: Optional[Callable] = None, workflow: Optional[str] = None) -> Callable:
    if f is None:
        return partial(generate_workflow, workflow=workflow)

    @wraps(f)
    def wrapper(environment: Environment, config: dict, writer: Callable) -> Any:
        def workflow_enabled() -> bool:
            return all(wf.get("enabled", True) for wf in config.get("workflows", []) if wf["name"] == workflow)

        if workflow_enabled():
            return f(environment, config, writer)

    return wrapper


@generate_workflow(workflow="create")
def generate_create_workflow(environment: Environment, config: dict, writer: Callable) -> None:
    product_block = root_product_block(config)
    validations, validation_imports = get_validations(config)

    template = environment.get_template("create_product.j2")
    content = template.render(
        product=config, product_block=product_block, validations=validations, validation_imports=validation_imports
    )

    path = get_workflow_path(config, "create")

    writer(path, content)


@generate_workflow(workflow="modify")
def generate_modify_workflow(environment: Environment, config: dict, writer: Callable) -> None:
    product_block = root_product_block(config)
    validations, validation_imports = get_validations_for_modify(config)

    template = environment.get_template("modify_product.j2")
    content = template.render(
        product=config, product_block=product_block, validations=validations, validation_imports=validation_imports
    )

    path = get_workflow_path(config, "modify")

    writer(path, content)


@generate_workflow(workflow="validate")
def generate_validate_workflow(environment: Environment, config: dict, writer: Callable) -> None:
    workflow = get_workflow(config, "validate")
    validations = workflow.get("validations", [])

    template = environment.get_template("validate_product.j2")
    content = template.render(product=config, validations=validations, product_types_module=product_types_module)

    path = get_workflow_path(config, "validate")

    writer(path, content)


@generate_workflow(workflow="terminate")
def generate_terminate_workflow(environment: Environment, config: dict, writer: Callable) -> None:
    workflow = get_workflow(config, "terminate")
    validations = workflow.get("validations", [])

    template = environment.get_template("terminate_product.j2")
    content = template.render(product=config, validations=validations, product_types_module=product_types_module)

    path = get_workflow_path(config, "terminate")

    writer(path, content)
