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

import json
from typing import Any

from orchestrator.targets import Target
from orchestrator.utils.errors import ProcessFailureError
from orchestrator.utils.json import json_dumps
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 pydantic_forms.types import State, UUIDstr

from gso.products.product_types.router import Router
from gso.services import infoblox, lso_client, subscriptions
from gso.services.librenms_client import LibreNMSClient
from gso.services.lso_client import anonymous_lso_interaction, execute_playbook
from gso.services.netbox_client import NetboxClient
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], callback_route: str) -> None:
    """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": subscriptions.get_active_pe_router_dict(),
        "verb": "verify_p_ibgp",
        "is_verification_workflow": "true",
    }

    lso_client.execute_playbook(
        playbook_name="update_ibgp_mesh.yaml",
        callback_route=callback_route,
        inventory=subscription["router"]["router_fqdn"],
        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("Check base config for drift")
def verify_base_config(subscription: Router, callback_route: str) -> None:
    """Workflow step for running a playbook that checks whether base config has drifted."""
    execute_playbook(
        playbook_name="base_config.yaml",
        callback_route=callback_route,
        inventory=subscription.router.router_fqdn,
        extra_vars={
            "wfo_router_json": json.loads(json_dumps(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)

    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
        >> anonymous_lso_interaction(verify_base_config)
        >> anonymous_lso_interaction(verify_p_ibgp)
        >> resync
        >> done
    )