{% extends "admin/base_site.html" %}
{% load i18n admin_urls %}

{% block breadcrumbs %}
{% if has_permission %}
<div class="breadcrumbs">
  <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
  &rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name|capfirst }}</a>
  &rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
  &rsaquo; {% trans "Register Visitor" %}
</div>
{% endif %}
{% endblock %}

{% block content %}
<div id="content-main">
  <style>
    #content-main .status-list {
      list-style: none;
      margin: 1.5rem 0;
      padding: 0;
    }

    #content-main .status-item,
    #content-main .status-banner {
      display: flex;
      align-items: flex-start;
      gap: 0.75rem;
    }

    #content-main .status-item {
      margin-bottom: 1rem;
    }

    #content-main .status-item:last-child {
      margin-bottom: 0;
    }

    #content-main .status-item.errornote {
      display: flex;
    }

    #content-main .status-indicator {
      width: 1.25rem;
      height: 1.25rem;
      border-radius: 999px;
      border: 2px solid transparent;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      font-size: 0.75rem;
      font-weight: 700;
      line-height: 1;
      flex-shrink: 0;
    }

    #content-main .status-indicator.is-pending {
      border-color: currentColor;
      border-top-color: transparent;
      animation: registration-spin 0.8s linear infinite;
      opacity: 0.7;
    }

    #content-main .status-indicator.is-success {
      border-color: rgba(25, 135, 84, 0.45);
      background-color: rgba(25, 135, 84, 0.15);
      color: #198754;
    }

    #content-main .status-indicator.is-error {
      border-color: rgba(220, 53, 69, 0.45);
      background-color: rgba(220, 53, 69, 0.2);
      color: #dc3545;
    }

    #content-main .status-text {
      flex: 1 1 auto;
    }

    #content-main .status-banner {
      margin: 1.5rem 0;
      padding: 0.75rem 1rem;
      border-radius: 0.5rem;
      background: rgba(11, 105, 193, 0.12);
      color: inherit;
    }

    #content-main .status-banner[data-status="success"] {
      background: rgba(25, 135, 84, 0.15);
    }

    #content-main .status-banner[data-status="error"] {
      background: rgba(220, 53, 69, 0.15);
    }

    @keyframes registration-spin {
      to {
        transform: rotate(360deg);
      }
    }

    @media (prefers-reduced-motion: reduce) {
      #content-main .status-indicator.is-pending {
        animation: none;
        border-top-color: currentColor;
      }
    }
  </style>
  <p>{% trans "Attempting to exchange node information between this host and the visiting instance." %}</p>
  <div id="result" class="status-banner" role="status" aria-live="polite" aria-busy="true" data-status="pending">
      <span class="status-indicator is-pending" aria-hidden="true"></span>
      <span class="status-text">{% trans "Registration in progress…" %}</span>
  </div>
  <ul class="status-list">
      <li id="host-result" class="status-item" data-status="pending">
          <span class="status-indicator is-pending" aria-hidden="true"></span>
          <span class="status-text">{% trans "Registering the visiting node on this server…" %}</span>
      </li>
      <li id="visitor-result" class="status-item" data-status="pending">
          <span class="status-indicator is-pending" aria-hidden="true"></span>
          <span class="status-text">{% trans "Registering this server on the visiting node…" %}</span>
      </li>
  </ul>
</div>
<script>
(function() {
    const token = "{{ token|escapejs }}";
    const hostInfoUrl = "{{ info_url|escapejs }}?token=" + token;
    const hostRegisterUrl = "{{ register_url|escapejs }}";
    const visitorInfoUrl = "{{ visitor_info_url|escapejs }}";
    const visitorRegisterUrl = "{{ visitor_register_url|escapejs }}";
    const hostResult = document.getElementById("host-result");
    const visitorResult = document.getElementById("visitor-result");
    const summary = document.getElementById("result");
    const messages = {
        hostRegistered: "{{ _('Visitor node registered with this server.')|escapejs }}",
        visitorRegistered: "{{ _('Host node registered with visitor.')|escapejs }}",
        success: "{{ _('Both nodes registered successfully.')|escapejs }}",
        partial: "{{ _('One or more registrations failed.')|escapejs }}",
        aborted: "{{ _('Registration aborted.')|escapejs }}",
        visitorInfoError: "{{ _('Unable to read visitor node information.')|escapejs }}",
        hostInfoError: "{{ _('Unable to read host node information.')|escapejs }}",
        fetchAdvice: "{{ _('Make sure the visiting node is reachable. Run "./manage.py check_registration_ready" on the visiting node to verify readiness.')|escapejs }}",
    };

    function updateIndicator(indicator, state) {
        if (!indicator) {
            return;
        }
        indicator.classList.remove("is-pending", "is-success", "is-error");
        indicator.textContent = "";
        if (state === "pending") {
            indicator.classList.add("is-pending");
            return;
        }
        if (state === "error") {
            indicator.classList.add("is-error");
            indicator.textContent = "!";
            return;
        }
        indicator.classList.add("is-success");
        indicator.textContent = "\u2713";
    }

    function setStatus(element, message, isError) {
        if (!element) {
            return;
        }
        const textTarget = element.querySelector(".status-text");
        if (textTarget) {
            textTarget.textContent = message;
        } else {
            element.textContent = message;
        }
        if (isError) {
            element.classList.add("errornote");
        } else {
            element.classList.remove("errornote");
        }
        if (element.dataset) {
            element.dataset.status = isError ? "error" : "success";
        }
        if (element.hasAttribute("aria-busy")) {
            element.setAttribute("aria-busy", "false");
        }
        updateIndicator(element.querySelector(".status-indicator"), isError ? "error" : "success");
    }

    async function loadJson(url, options) {
        const response = await fetch(url, options);
        const text = await response.text();
        let payload;
        try {
            payload = JSON.parse(text);
        } catch (err) {
            payload = text;
        }
        if (!response.ok) {
            const detail = payload && payload.detail ? payload.detail : text || response.statusText;
            throw new Error(detail);
        }
        return payload;
    }

    function buildUrlVariants(url) {
        const variants = [];
        const seen = new Set();

        function push(value) {
            if (value && !seen.has(value)) {
                seen.add(value);
                variants.push(value);
            }
        }

        try {
            const parsed = new URL(url, window.location.href);
            const baseVariants = [];
            if (parsed.protocol === "http:") {
                const secure = new URL(parsed.href);
                secure.protocol = "https:";
                baseVariants.push(secure);
            }
            baseVariants.push(parsed);

            const normalizedHost = parsed.hostname || "";
            const hostKey = normalizedHost.replace(/^[\[]|[\]]$/g, "");
            const hostAlternatives = [normalizedHost];

            if (hostKey === "localhost") {
                hostAlternatives.push("127.0.0.1", "::1");
            } else if (hostKey === "127.0.0.1") {
                hostAlternatives.push("localhost", "::1");
            } else if (hostKey === "::1") {
                hostAlternatives.push("localhost", "127.0.0.1");
            }

            const uniqueHosts = hostAlternatives.filter(
                (value, index, self) => value && self.indexOf(value) === index
            );

            for (const base of baseVariants) {
                push(base.href);
                for (const host of uniqueHosts) {
                    if (host === base.hostname) {
                        continue;
                    }
                    const candidate = new URL(base.href);
                    candidate.hostname = host;
                    push(candidate.href);
                }
            }
        } catch (err) {
            push(url);
        }

        return variants;
    }

    async function requestWithFallback(urls, options) {
        const targets = Array.isArray(urls) ? urls : [urls];
        let lastError = null;
        for (const target of targets) {
            try {
                return await loadJson(target, options);
            } catch (error) {
                const message = error && error.message ? error.message : "";
                if (error instanceof TypeError || message === "Failed to fetch") {
                    lastError = error;
                    continue;
                }
                throw error;
            }
        }
        if (lastError) {
            throw lastError;
        }
        throw new Error("Request failed");
    }

    function formatError(error) {
        let message = "";
        if (error && typeof error.message === "string") {
            message = error.message;
        } else if (typeof error === "string") {
            message = error;
        } else if (error) {
            message = String(error);
        }
        if (message === "Failed to fetch") {
            return `${message} — ${messages.fetchAdvice}`;
        }
        return message || messages.partial;
    }

    function cleanRole(info) {
        if (!info || typeof info !== "object") {
            return "";
        }
        const roleSources = [info.role, info.role_name];
        for (const value of roleSources) {
            if (typeof value !== "string") {
                continue;
            }
            const cleaned = value.trim();
            if (cleaned) {
                return cleaned;
            }
        }
        return "";
    }

    function buildPayload(info, relation) {
        const payload = {
            hostname: info.hostname,
            address: info.address,
            port: info.port,
            mac_address: info.mac_address,
            public_key: info.public_key,
            features: info.features || [],
        };
        payload.trusted = true;
        const relationValue = relation || info.current_relation;
        if (relationValue) {
            payload.current_relation = relationValue;
        }
        const roleValue = cleanRole(info);
        if (roleValue) {
            payload.role = roleValue;
        }
        return payload;
    }

    function addSignature(payload, info) {
        if (info.token_signature) {
            payload.token = token;
            payload.signature = info.token_signature;
        }
    }

    (async function() {
        try {
            const visitorInfoTargets = buildUrlVariants(visitorInfoUrl + "?token=" + token);
            const visitorInfoPromise = requestWithFallback(visitorInfoTargets);
            const hostInfoPromise = loadJson(hostInfoUrl);
            const [visitorInfo, hostInfo] = await Promise.all([
                visitorInfoPromise,
                hostInfoPromise,
            ]);

            if (!visitorInfo || typeof visitorInfo !== "object") {
                throw new Error(messages.visitorInfoError);
            }
            if (!hostInfo || typeof hostInfo !== "object") {
                throw new Error(messages.hostInfoError);
            }

            const hostPayload = buildPayload(visitorInfo, "Downstream");
            addSignature(hostPayload, visitorInfo);
            try {
                const result = await loadJson(hostRegisterUrl, {
                    method: "POST",
                    headers: {"Content-Type": "application/json"},
                    credentials: "same-origin",
                    body: JSON.stringify(hostPayload),
                });
                const detail = result && typeof result === "object" ? result.detail : "";
                const identifier = result && typeof result === "object" ? result.id : null;
                if (!identifier) {
                    const errorMessage = detail || messages.partial;
                    throw new Error(errorMessage);
                }
                setStatus(hostResult, detail || messages.hostRegistered, false);
            } catch (error) {
                setStatus(hostResult, formatError(error), true);
            }

            const visitorPayload = buildPayload(hostInfo, "Upstream");
            addSignature(visitorPayload, hostInfo);
            try {
                const visitorRegisterTargets = buildUrlVariants(visitorRegisterUrl);
                const result = await requestWithFallback(visitorRegisterTargets, {
                    method: "POST",
                    credentials: "include",
                    body: JSON.stringify(visitorPayload),
                });
                const detail = result && typeof result === "object" ? result.detail : "";
                const identifier = result && typeof result === "object" ? result.id : null;
                if (!identifier) {
                    const errorMessage = detail || messages.partial;
                    throw new Error(errorMessage);
                }
                setStatus(visitorResult, detail || messages.visitorRegistered, false);
            } catch (error) {
                setStatus(visitorResult, formatError(error), true);
            }

            if (!hostResult.classList.contains("errornote") && !visitorResult.classList.contains("errornote")) {
                setStatus(summary, messages.success, false);
            } else {
                setStatus(summary, messages.partial, true);
            }
        } catch (error) {
            const message = formatError(error);
            setStatus(summary, message, true);
            if (!hostResult.classList.contains("errornote")) {
                setStatus(hostResult, messages.aborted, true);
            }
            if (!visitorResult.classList.contains("errornote")) {
                setStatus(visitorResult, messages.aborted, true);
            }
        }
    })();
})();
</script>
{% endblock %}
