"""Router validation workflow. Used in a nightly schedule."""

from typing import Any

from orchestrator.targets import Target
from orchestrator.types import State, UUIDstr
from orchestrator.utils.errors import ProcessFailureError
from orchestrator.workflow import StepList, begin, conditional, done, step, workflow
from orchestrator.workflows.steps import resync, 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
from gso.services.kentik_client import KentikClient
from gso.services.librenms_client import LibreNMSClient
from gso.services.lso_client import LSOState, anonymous_lso_interaction
from gso.services.netbox_client import NetboxClient
from gso.utils.helpers import generate_inventory_for_routers
from gso.utils.shared_enums import Vendor


@step("Prepare required keys in state")
def prepare_state(subscription_id: UUIDstr) -> State:
    """Add required keys to the state for the workflow to run successfully."""
    router = Router.from_subscription(subscription_id)

    return {"subscription": router}


@step("Verify IPAM resources for loopback interface")
def verify_ipam_loopback(subscription: Router) -> None:
    """Validate the :term:`IPAM` resources for the loopback interface.

    Raises an :class:`orchestrator.utils.errors.ProcessFailureError` if :term:`IPAM` is configured incorrectly.
    """
    host_record = infoblox.find_host_by_fqdn(f"lo0.{subscription.router.router_fqdn}")
    if not host_record or str(subscription.subscription_id) not in host_record.comment:
        msg = "Loopback record is incorrectly configured in IPAM, please investigate this manually!"
        raise ProcessFailureError(msg)


@step("Verify correct Netbox entry")
def check_netbox_entry_exists(subscription: Router) -> None:
    """Validate the Netbox entry for a Router.

    This will only ensure existence of the node itself in Netbox. Validation of separate interfaces takes places in
    other subscriptions' validation workflows.
    """
    client = NetboxClient()
    #  Try and fetch the host, which will raise an exception on failure.
    client.get_device_by_name(subscription.router.router_fqdn)


@step("Verify BGP configuration on P router")
def verify_p_ibgp(subscription: dict[str, Any]) -> LSOState:
    """Perform a dry run of adding the list of all PE routers to the new P router."""
    extra_vars = {
        "dry_run": True,
        "subscription": subscription,
        "pe_router_list": generate_inventory_for_routers(RouterRole.PE)["all"]["hosts"],
        "verb": "verify_p_ibgp",
        "is_verification_workflow": "true",
    }

    return {
        "playbook_name": "gap_ansible/playbooks/update_ibgp_mesh.yaml",
        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
        "extra_vars": extra_vars,
    }


@step("Verify correct LibreNMS entry")
def check_librenms_entry_exists(subscription: Router) -> None:
    """Validate the LibreNMS entry for a Router.

    Raises an HTTP error 404 when the device is not present in LibreNMS.
    """
    client = LibreNMSClient()
    errors = client.validate_device(subscription.router.router_fqdn)
    if errors:
        raise ProcessFailureError(message="LibreNMS configuration error", details=errors)


@step("Verify Kentik entry for PE router")
def check_kentik_entry_exists(subscription: Router) -> None:
    """Validate the Kentik entry for a PE Router.

    Raises an HTTP error 404 when the device is not present in Kentik.
    """
    client = KentikClient()
    device = client.get_device_by_name(subscription.router.router_fqdn)
    if not device:
        raise ProcessFailureError(
            message="Device not found in Kentik", details={"device": subscription.router.router_fqdn}
        )


@step("Check base config for drift")
def verify_base_config(subscription: dict[str, Any]) -> LSOState:
    """Workflow step for running a playbook that checks whether base config has drifted."""
    return {
        "playbook_name": "gap_ansible/playbooks/base_config.yaml",
        "inventory": {"all": {"hosts": {subscription["router"]["router_fqdn"]: None}}},
        "extra_vars": {
            "wfo_router_json": subscription,
            "verb": "deploy",
            "dry_run": "true",
            "is_verification_workflow": "true",
        },
    }


@workflow(
    "Validate router configuration", target=Target.SYSTEM, initial_input_form=wrap_modify_initial_input_form(None)
)
def validate_router() -> StepList:
    """Validate an existing, active Router subscription.

    * Verify that the loopback interface is correctly configured in :term:`IPAM`.
    * Verify that the router is correctly configured in Netbox.
    * Verify that the router is correctly configured in LibreNMS.
    * Redeploy base config to verify the configuration is intact.
    * Validate configuration of the iBGP mesh
    """
    is_juniper_router = conditional(lambda state: state["subscription"]["router"]["vendor"] == Vendor.JUNIPER)
    is_pe_router = conditional(lambda state: state["subscription"]["router"]["router_role"] == RouterRole.PE)

    return (
        begin
        >> store_process_subscription(Target.SYSTEM)
        >> prepare_state
        >> is_juniper_router(done)
        >> unsync
        >> verify_ipam_loopback
        >> check_netbox_entry_exists
        >> check_librenms_entry_exists
        >> is_pe_router(check_kentik_entry_exists)
        >> anonymous_lso_interaction(verify_base_config)
        >> anonymous_lso_interaction(verify_p_ibgp)
        >> resync
        >> done
    )