"""A workflow that terminates a router."""

import ipaddress
import json
import logging

from orchestrator.forms import FormPage
from orchestrator.forms.validators import Label
from orchestrator.targets import Target
from orchestrator.types import FormGenerator, SubscriptionLifecycle, UUIDstr
from orchestrator.utils.json import json_dumps
from orchestrator.workflow import StepList, begin, conditional, done, step, workflow
from orchestrator.workflows.steps import (
    resync,
    set_status,
    store_process_subscription,
    unsync,
)
from orchestrator.workflows.utils import wrap_modify_initial_input_form

from gso.products.product_blocks.router import RouterRole
from gso.products.product_types.router import Router
from gso.services import infoblox, lso_client
from gso.services.librenms_client import LibreNMSClient
from gso.services.lso_client import execute_playbook, lso_interaction
from gso.services.netbox_client import NetboxClient
from gso.utils.helpers import generate_inventory_for_active_routers
from gso.utils.shared_enums import Vendor

logger = logging.getLogger(__name__)


def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator:
    """Let the operator decide whether to delete configuration on the router, and clear up :term:`IPAM` resources."""
    router = Router.from_subscription(subscription_id)

    class TerminateForm(FormPage):
        if router.status == SubscriptionLifecycle.INITIAL:
            info_label_2: Label = (
                "This will immediately mark the subscription as terminated, preventing any other workflows from "
                "interacting with this product subscription."
            )
            info_label_3: Label = "ONLY EXECUTE THIS WORKFLOW WHEN YOU ARE ABSOLUTELY SURE WHAT YOU ARE DOING."

        tt_number: str
        termination_label: Label = "Please confirm whether configuration should get removed from the router."
        remove_configuration: bool = True
        update_ibgp_mesh_label: Label = "Please confirm whether the iBGP mesh should get updated."
        update_ibgp_mesh: bool = True

    user_input = yield TerminateForm
    return user_input.model_dump() | {
        "router_is_nokia": router.router.vendor == Vendor.NOKIA,
        "router_role": router.router.router_role,
    }


@step("Deprovision loopback IPs from IPAM")
def deprovision_loopback_ips(subscription: Router) -> dict:
    """Clear up the loopback addresses from :term:`IPAM`."""
    infoblox.delete_host_by_ip(ipaddress.IPv4Address(subscription.router.router_lo_ipv4_address))

    return {"subscription": subscription}


@step("[DRY RUN] Remove configuration from router")
def remove_config_from_router_dry(
    subscription: Router, callback_route: str, process_id: UUIDstr, tt_number: str
) -> None:
    """Remove configuration from the router, first as a dry run."""
    extra_vars = {
        "wfo_router_json": json.loads(json_dumps(subscription)),
        "dry_run": True,
        "verb": "terminate",
        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Terminating "
        f"{subscription.router.router_fqdn}",
    }

    execute_playbook(
        playbook_name="base_config.yaml",
        callback_route=callback_route,
        inventory=subscription.router.router_fqdn,
        extra_vars=extra_vars,
    )


@step("[FOR REAL] Remove configuration from router")
def remove_config_from_router_real(
    subscription: Router, callback_route: str, process_id: UUIDstr, tt_number: str
) -> None:
    """Remove configuration from the router."""
    extra_vars = {
        "wfo_router_json": json.loads(json_dumps(subscription)),
        "dry_run": False,
        "verb": "terminate",
        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - Terminating "
        f"{subscription.router.router_fqdn}",
    }

    execute_playbook(
        playbook_name="base_config.yaml",
        callback_route=callback_route,
        inventory=subscription.router.router_fqdn,
        extra_vars=extra_vars,
    )


@step("Remove Device from Netbox")
def remove_device_from_netbox(subscription: Router) -> dict[str, Router]:
    """Remove the device from Netbox."""
    NetboxClient().delete_device(subscription.router.router_fqdn)
    return {"subscription": subscription}


@step("[DRY RUN] Remove P router from all the PE routers")
def remove_p_from_all_pe_dry(
    subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr
) -> None:
    """Perform a dry run of removing the terminated router from all the PE routers."""
    extra_vars = {
        "dry_run": True,
        "subscription": subscription,
        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
        f"Remove {subscription.router.router_fqdn} from all the PE routers",
        "verb": "remove_p_from_pe",
    }

    lso_client.execute_playbook(
        playbook_name="update_ibgp_mesh.yaml",
        callback_route=callback_route,
        inventory=generate_inventory_for_active_routers(RouterRole.PE),
        extra_vars=extra_vars,
    )


@step("[REAL RUN] Remove P router from all the PE routers")
def remove_p_from_all_pe_real(
    subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr
) -> None:
    """Perform a real run of removing the terminated router from all the PE routers."""
    extra_vars = {
        "dry_run": False,
        "subscription": subscription,
        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
        f"Remove {subscription.router.router_fqdn} from all the PE routers",
        "verb": "remove_p_from_pe",
    }

    lso_client.execute_playbook(
        playbook_name="update_ibgp_mesh.yaml",
        callback_route=callback_route,
        inventory=generate_inventory_for_active_routers(RouterRole.PE),
        extra_vars=extra_vars,
    )


@step("[DRY RUN] Remove PE router from all the PE routers")
def remove_pe_from_all_pe_dry(
    subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr
) -> None:
    """Perform a dry run of removing the terminated router from all the PE routers."""
    extra_vars = {
        "dry_run": True,
        "subscription": subscription,
        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
        f"Remove {subscription.router.router_fqdn} from all the PE routers",
        "verb": "remove_p_from_net",
    }

    lso_client.execute_playbook(
        playbook_name="update_ibgp_mesh.yaml",
        callback_route=callback_route,
        inventory=generate_inventory_for_active_routers(
            RouterRole.PE, exclude_routers=[subscription.router.router_fqdn]),
        extra_vars=extra_vars,
    )


@step("[REAL RUN] Remove all PE routers from all the PE routers")
def remove_pe_from_all_pe_real(
    subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr
) -> None:
    """Perform a real run of removing PE router from P router iBGP table."""
    extra_vars = {
        "dry_run": False,
        "subscription": subscription,
        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
        f"Remove {subscription.router.router_fqdn} from iBGP mesh",
        "verb": "remove_p_from_net",
    }

    lso_client.execute_playbook(
        playbook_name="update_ibgp_mesh.yaml",
        callback_route=callback_route,
        inventory=generate_inventory_for_active_routers(
            RouterRole.PE, exclude_routers=[subscription.router.router_fqdn]),
        extra_vars=extra_vars,
    )


@step("[DRY RUN] Remove PE router from all the P routers")
def remove_pe_from_all_p_dry(
    subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr
) -> None:
    """Perform a dry run of removing PE router from all P routers."""
    extra_vars = {
        "dry_run": True,
        "subscription": subscription,
        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
        f"Remove {subscription.router.router_fqdn} from all the P routers",
        "verb": "remove_pe_from_p",
    }

    lso_client.execute_playbook(
        playbook_name="update_ibgp_mesh.yaml",
        callback_route=callback_route,
        inventory=generate_inventory_for_active_routers(RouterRole.P),
        extra_vars=extra_vars,
    )


@step("[REAL RUN] Remove PE router from all P routers")
def remove_pe_from_all_p_real(
    subscription: Router, callback_route: str, tt_number: str, process_id: UUIDstr
) -> None:
    """Perform a dry run of removing PE router from all P routers."""
    extra_vars = {
        "dry_run": False,
        "subscription": subscription,
        "commit_comment": f"GSO_PROCESS_ID: {process_id} - TT_NUMBER: {tt_number} - "
        f"Remove {subscription.router.router_fqdn} from all the P routers",
        "verb": "remove_pe_from_p",
    }

    lso_client.execute_playbook(
        playbook_name="update_ibgp_mesh.yaml",
        callback_route=callback_route,
        inventory=generate_inventory_for_active_routers(RouterRole.P),
        extra_vars=extra_vars,
    )


@step("Remove Device from Librenms")
def remove_device_from_librenms(subscription: Router) -> dict[str, Router]:
    """Remove the device from LibreNMS."""
    LibreNMSClient().remove_device(subscription.router.router_fqdn)
    return {"subscription": subscription}


@workflow(
    "Terminate router",
    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
    target=Target.TERMINATE,
)
def terminate_router() -> StepList:
    """Terminate a router subscription.

    * Let the operator decide whether to delete :term:`IPAM` resources, and remove configuration from the router
    * Clear up :term:`IPAM` resources, if selected by the operator
    * Disable and delete configuration on the router, if selected by the operator
    * Mark the subscription as terminated in the service database
    """
    run_config_steps = conditional(lambda state: state["remove_configuration"])
    update_ibgp_mesh = conditional(lambda state: state["update_ibgp_mesh"])
    router_is_nokia = conditional(lambda state: state["router_is_nokia"])
    router_is_pe = conditional(lambda state: state["router_role"] == RouterRole.PE)
    router_is_p = conditional(lambda state: state["router_role"] == RouterRole.P)

    return (
        begin
        >> store_process_subscription(Target.TERMINATE)
        >> unsync
        >> update_ibgp_mesh(router_is_p(lso_interaction(remove_p_from_all_pe_dry)))
        >> update_ibgp_mesh(router_is_p(lso_interaction(remove_p_from_all_pe_real)))
        >> update_ibgp_mesh(router_is_pe(lso_interaction(remove_pe_from_all_pe_dry)))
        >> update_ibgp_mesh(router_is_pe(lso_interaction(remove_pe_from_all_pe_real)))
        >> update_ibgp_mesh(router_is_pe(lso_interaction(remove_pe_from_all_p_dry)))
        >> update_ibgp_mesh(router_is_pe(lso_interaction(remove_pe_from_all_p_real)))
        >> deprovision_loopback_ips
        >> run_config_steps(lso_interaction(remove_config_from_router_dry))
        >> run_config_steps(lso_interaction(remove_config_from_router_real))
        >> router_is_nokia(remove_device_from_netbox)
        >> remove_device_from_librenms
        >> set_status(SubscriptionLifecycle.TERMINATED)
        >> resync
        >> done
    )