# Copyright 2024-2026 The DNS-AID Authors
# SPDX-License-Identifier: Apache-2.0

"""
DNS-AID Publisher: Create DNS records for AI agent discovery.

This module handles publishing agents to DNS using SVCB and TXT records
as specified in IETF draft-mozleywilliams-dnsop-bandaid-02.
"""

from __future__ import annotations

import structlog

from dns_aid.backends.base import DNSBackend
from dns_aid.backends.mock import MockBackend
from dns_aid.core.models import AgentRecord, Protocol, PublishResult

logger = structlog.get_logger(__name__)

# Global default backend (can be overridden)
_default_backend: DNSBackend | None = None


def set_default_backend(backend: DNSBackend) -> None:
    """Set the default DNS backend for publish operations."""
    global _default_backend
    _default_backend = backend


def reset_default_backend() -> None:
    """Reset the default backend so it will be re-initialized on next call."""
    global _default_backend
    _default_backend = None


def get_default_backend() -> DNSBackend:
    """Get the default DNS backend based on DNS_AID_BACKEND env var.

    Supported values: route53, cloudflare, infoblox, ddns, mock

    Raises:
        ValueError: If DNS_AID_BACKEND is not set (no silent fallback to mock).
    """
    import os

    global _default_backend
    if _default_backend is None:
        backend_type = os.environ.get("DNS_AID_BACKEND", "").lower()

        if not backend_type:
            raise ValueError(
                "DNS_AID_BACKEND must be set. "
                "Supported values: route53, cloudflare, infoblox, ddns, mock"
            )

        if backend_type == "route53":
            from dns_aid.backends.route53 import Route53Backend

            _default_backend = Route53Backend()
        elif backend_type == "cloudflare":
            from dns_aid.backends.cloudflare import CloudflareBackend

            _default_backend = CloudflareBackend()
        elif backend_type == "infoblox":
            from dns_aid.backends.infoblox import InfobloxBackend

            _default_backend = InfobloxBackend()
        elif backend_type == "ddns":
            from dns_aid.backends.ddns import DDNSBackend

            _default_backend = DDNSBackend()
        elif backend_type == "mock":
            _default_backend = MockBackend()
        else:
            raise ValueError(
                f"Unknown DNS_AID_BACKEND: '{backend_type}'. "
                "Supported values: route53, cloudflare, infoblox, ddns, mock"
            )

        logger.info(
            "Initialized default DNS backend",
            backend=backend_type,
            backend_name=_default_backend.name,
        )
    return _default_backend


async def publish(
    name: str,
    domain: str,
    protocol: str | Protocol,
    endpoint: str,
    port: int = 443,
    capabilities: list[str] | None = None,
    version: str = "1.0.0",
    description: str | None = None,
    use_cases: list[str] | None = None,
    category: str | None = None,
    ttl: int = 3600,
    backend: DNSBackend | None = None,
    cap_uri: str | None = None,
    cap_sha256: str | None = None,
    bap: list[str] | None = None,
    policy_uri: str | None = None,
    realm: str | None = None,
    sign: bool = False,
    private_key_path: str | None = None,
) -> PublishResult:
    """
    Publish an AI agent to DNS using DNS-AID protocol.

    Creates SVCB and TXT records that allow other agents to discover
    this agent via DNS queries.

    Args:
        name: Agent identifier (e.g., "chat", "network-specialist")
        domain: Domain to publish under (e.g., "example.com")
        protocol: Communication protocol ("a2a", "mcp", or Protocol enum)
        endpoint: Hostname where agent is reachable
        port: Port number (default: 443)
        capabilities: List of agent capabilities
        version: Agent version string
        description: Human-readable description
        use_cases: List of use cases for this agent
        category: Agent category (e.g., "network", "security")
        ttl: DNS record TTL in seconds
        backend: DNS backend to use (defaults to global backend)
        cap_uri: URI to capability document (BANDAID draft-compliant)
        cap_sha256: Base64url-encoded SHA-256 digest of the capability descriptor
        bap: Supported bulk agent protocols (e.g., ["mcp", "a2a"])
        policy_uri: URI to agent policy document
        realm: Multi-tenant scope identifier (e.g., "production")
        sign: If True, sign the record with JWS (requires private_key_path)
        private_key_path: Path to EC P-256 private key PEM file for signing

    Returns:
        PublishResult with created records

    Example:
        >>> result = await publish(
        ...     name="network-specialist",
        ...     domain="example.com",
        ...     protocol="mcp",
        ...     endpoint="mcp.example.com",
        ...     capabilities=["ipam", "dns", "vpn"],
        ...     cap_uri="https://mcp.example.com/.well-known/agent-cap.json",
        ...     realm="production",
        ... )
        >>> print(result.agent.fqdn)
        '_network-specialist._mcp._agents.example.com'
    """
    # Normalize protocol to enum
    if isinstance(protocol, str):
        protocol = Protocol(protocol.lower())

    # Generate JWS signature if requested
    sig = None
    if sign:
        if not private_key_path:
            raise ValueError("private_key_path is required when sign=True")

        from dns_aid.core.jwks import (
            RecordPayload,
            load_private_key_from_pem,
            sign_record,
        )

        logger.info("Signing record with JWS", private_key_path=private_key_path)
        private_key = load_private_key_from_pem(private_key_path)
        fqdn = f"_{name}._{protocol.value}._agents.{domain}"
        payload = RecordPayload.from_agent_record(
            fqdn=fqdn,
            target=endpoint,
            port=port,
            protocol=protocol.value,
            ttl_seconds=ttl,
        )
        sig = sign_record(payload, private_key)
        logger.info("Record signed successfully", fqdn=fqdn)

    # Create agent record
    agent = AgentRecord(
        name=name,
        domain=domain,
        protocol=protocol,
        target_host=endpoint,
        port=port,
        capabilities=capabilities or [],
        version=version,
        description=description,
        use_cases=use_cases or [],
        category=category,
        ttl=ttl,
        cap_uri=cap_uri,
        cap_sha256=cap_sha256,
        bap=bap or [],
        policy_uri=policy_uri,
        realm=realm,
        sig=sig,
    )

    # Get backend
    dns_backend = backend or get_default_backend()

    logger.info(
        "Publishing agent to DNS",
        agent_name=agent.name,
        domain=agent.domain,
        protocol=agent.protocol.value,
        fqdn=agent.fqdn,
        backend=dns_backend.name,
    )

    # Check zone exists
    if not await dns_backend.zone_exists(domain):
        logger.error("Zone does not exist", zone=domain)
        return PublishResult(
            agent=agent,
            records_created=[],
            zone=domain,
            backend=dns_backend.name,
            success=False,
            message=f"Zone '{domain}' does not exist or is not accessible",
        )

    try:
        # Create DNS records
        records = await dns_backend.publish_agent(agent)

        logger.info(
            "Agent published successfully",
            fqdn=agent.fqdn,
            records=records,
        )

        return PublishResult(
            agent=agent,
            records_created=records,
            zone=domain,
            backend=dns_backend.name,
            success=True,
            message="Agent published successfully",
        )

    except Exception as e:
        logger.exception("Failed to publish agent", error=str(e))
        return PublishResult(
            agent=agent,
            records_created=[],
            zone=domain,
            backend=dns_backend.name,
            success=False,
            message=f"Failed to publish: {e}",
        )


async def unpublish(
    name: str,
    domain: str,
    protocol: str | Protocol,
    backend: DNSBackend | None = None,
) -> bool:
    """
    Remove an agent from DNS.

    Deletes both SVCB and TXT records for the agent.

    Args:
        name: Agent identifier
        domain: Domain where agent is published
        protocol: Communication protocol
        backend: DNS backend to use

    Returns:
        True if records were deleted
    """
    # Normalize protocol
    if isinstance(protocol, str):
        protocol = Protocol(protocol.lower())

    dns_backend = backend or get_default_backend()

    record_name = f"_{name}._{protocol.value}._agents"

    logger.info(
        "Removing agent from DNS",
        agent_name=name,
        domain=domain,
        record_name=record_name,
    )

    # Delete both record types
    svcb_deleted = await dns_backend.delete_record(domain, record_name, "SVCB")
    txt_deleted = await dns_backend.delete_record(domain, record_name, "TXT")

    success = svcb_deleted or txt_deleted

    if success:
        logger.info("Agent removed from DNS", agent_name=name)
    else:
        logger.warning("No records found to delete", agent_name=name)

    return success
