"""A termination workflow for an active IP trunk."""

import ipaddress

from orchestrator.forms import FormPage
from orchestrator.forms.validators import Label
from orchestrator.targets import Target
from orchestrator.types import FormGenerator, State, SubscriptionLifecycle, UUIDstr
from orchestrator.workflow import StepList, conditional, done, init, 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.iptrunk import IptrunkSideBlock
from gso.products.product_blocks.router import RouterVendor
from gso.products.product_types.iptrunk import Iptrunk
from gso.services import infoblox, provisioning_proxy
from gso.services.netbox_client import NetboxClient
from gso.services.provisioning_proxy import pp_interaction
from gso.utils.helpers import get_router_vendor, set_isis_to_90000


def initial_input_form_generator() -> FormGenerator:
    """Ask the operator to confirm whether router configuration and :term:`IPAM` resources should be deleted."""

    class TerminateForm(FormPage):
        termination_label: Label = (
            "Please confirm whether configuration should get removed from the A and B sides of the trunk, and whether "
            "IPAM and Netbox resources should be released."  # type: ignore[assignment]
        )
        tt_number: str
        remove_configuration: bool = True
        clean_up_ipam: bool = True
        clean_up_netbox: bool = True

    user_input = yield TerminateForm
    return user_input.dict()


@step("Drain traffic from trunk")
def drain_traffic_from_ip_trunk(
    subscription: Iptrunk,
    process_id: UUIDstr,
    callback_route: str,
    tt_number: str,
) -> State:
    """Drain all traffic from the trunk.

    XXX: Should this not be done with the isis-90k-step?
    """
    provisioning_proxy.provision_ip_trunk(
        subscription,
        process_id,
        callback_route,
        tt_number,
        "isis_interface",
        dry_run=False,
    )

    return {"subscription": subscription}


@step("Deprovision IP trunk [DRY RUN]")
def deprovision_ip_trunk_dry(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
    """Perform a dry run of deleting configuration from the routers."""
    provisioning_proxy.deprovision_ip_trunk(subscription, process_id, callback_route, tt_number, dry_run=True)

    return {"subscription": subscription}


@step("Deprovision IP trunk [FOR REAL]")
def deprovision_ip_trunk_real(subscription: Iptrunk, process_id: UUIDstr, callback_route: str, tt_number: str) -> State:
    """Delete configuration from the routers."""
    provisioning_proxy.deprovision_ip_trunk(subscription, process_id, callback_route, tt_number, dry_run=False)

    return {"subscription": subscription}


def _remove_interface_from_netbox(side_block: IptrunkSideBlock) -> None:
    nbclient = NetboxClient()

    for member in side_block.iptrunk_side_ae_members:
        nbclient.free_interface(side_block.iptrunk_side_node.router_fqdn, member.interface_name)

    if side_block.iptrunk_side_ae_iface:
        nbclient.delete_interface(side_block.iptrunk_side_node.router_fqdn, side_block.iptrunk_side_ae_iface)


@step("Netbox: Remove interfaces on side A")
def netbox_remove_side_a_interfaces(subscription: Iptrunk) -> State:
    """Mark used interfaces on side A as free in Netbox."""
    _remove_interface_from_netbox(subscription.iptrunk.iptrunk_sides[0])

    return {"subscription": subscription}


@step("Netbox: Remove interfaces on side B")
def netbox_remove_side_b_interfaces(subscription: Iptrunk) -> State:
    """Mark used interfaces on side B as free in Netbox."""
    _remove_interface_from_netbox(subscription.iptrunk.iptrunk_sides[1])

    return {"subscription": subscription}


@step("Deprovision IPv4 networks")
def deprovision_ip_trunk_ipv4(subscription: Iptrunk) -> dict:
    """Clear up IPv4 resources in :term:`IPAM`."""
    infoblox.delete_network(ipaddress.IPv4Network(subscription.iptrunk.iptrunk_ipv4_network))

    return {"subscription": subscription}


@step("Deprovision IPv6 networks")
def deprovision_ip_trunk_ipv6(subscription: Iptrunk) -> dict:
    """Clear up IPv6 resources in :term:`IPAM`."""
    infoblox.delete_network(ipaddress.IPv6Network(subscription.iptrunk.iptrunk_ipv6_network))

    return {"subscription": subscription}


@workflow(
    "Terminate IPtrunk",
    initial_input_form=wrap_modify_initial_input_form(initial_input_form_generator),
    target=Target.TERMINATE,
)
def terminate_iptrunk() -> StepList:
    """Terminate an IP trunk.

    * Let the operator decide whether to remove configuration from the routers, if so:
        * Set the :term:`ISIS` metric of the IP trunk to an arbitrarily high value
        * Disable and remove configuration from the routers, first as a dry run
    * Mark the IP trunk interfaces as free in Netbox, if selected by the operator
    * Clear :term:`IPAM` resources, if selected by the operator
    * Terminate the subscription in the service database
    """
    run_config_steps = conditional(lambda state: state["remove_configuration"])
    run_ipam_steps = conditional(lambda state: state["clean_up_ipam"])
    side_a_is_nokia = conditional(
        lambda state: state["clean_up_netbox"]
        and get_router_vendor(
            state["subscription"]["iptrunk"]["iptrunk_sides"][0]["iptrunk_side_node"]["owner_subscription_id"]
        )
        == RouterVendor.NOKIA
    )
    side_b_is_nokia = conditional(
        lambda state: state["clean_up_netbox"]
        and get_router_vendor(
            state["subscription"]["iptrunk"]["iptrunk_sides"][1]["iptrunk_side_node"]["owner_subscription_id"]
        )
        == RouterVendor.NOKIA
    )

    config_steps = (
        init
        >> pp_interaction(set_isis_to_90000)
        >> pp_interaction(deprovision_ip_trunk_dry)
        >> pp_interaction(deprovision_ip_trunk_real)
    )
    ipam_steps = init >> deprovision_ip_trunk_ipv4 >> deprovision_ip_trunk_ipv6

    return (
        init
        >> store_process_subscription(Target.TERMINATE)
        >> unsync
        >> run_config_steps(config_steps)
        >> side_a_is_nokia(netbox_remove_side_a_interfaces)
        >> side_b_is_nokia(netbox_remove_side_b_interfaces)
        >> run_ipam_steps(ipam_steps)
        >> set_status(SubscriptionLifecycle.TERMINATED)
        >> resync
        >> done
    )