// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package main

import "fmt"

type managedNaviFunnelPayload struct {
	Edge         map[string]any                `json:"edge"`
	Services     []managedNaviFunnelSyncRecord `json:"services"`
	ServiceCount int                           `json:"serviceCount"`
	GeneratedAt  string                        `json:"generatedAt,omitempty"`
	SyncDeferred bool                          `json:"syncDeferred"`
}

type managedNaviFunnelSyncRecord struct {
	OrgID   int64                    `json:"orgId"`
	Service map[string]any           `json:"service"`
	Domain  map[string]any           `json:"domain"`
	Cert    map[string]any           `json:"cert"`
	Public  managedNaviFunnelPublic  `json:"public"`
	Backend managedNaviFunnelBackend `json:"backend"`
}

type managedNaviFunnelPublic struct {
	Host         string `json:"host"`
	ListenProto  string `json:"listenProto"`
	ListenPort   int    `json:"listenPort"`
	MountPath    string `json:"mountPath"`
	ListenerMode string `json:"listenerMode"`
	TLSMode      string `json:"tlsMode"`
}

type managedNaviFunnelBackend struct {
	Network string `json:"network"`
	Address string `json:"address"`
	Port    int    `json:"port"`
	Scheme  string `json:"scheme"`
}

func (m *managedNavi) PullFunnel() error {
	reqBody := managedNaviPollNodesRequest{
		Hostinfo: &managedNaviHostinfo{
			FrontendLogID: "derper",
			BackendLogID:  m.derpID,
		},
	}
	var payload managedNaviFunnelPayload
	if err := m.postControlJSON("/navi/funnel", reqBody, &payload); err != nil {
		return fmt.Errorf("Mirage pull funnel payload failed: %w", err)
	}
	m.storeFunnelPayload(&payload)
	if err := m.applyStoredFunnelPayload(); err != nil {
		return fmt.Errorf("Mirage apply funnel payload failed: %w", err)
	}
	m.logf("Mirage funnel payload updated: %d service(s), deferred=%v", payload.ServiceCount, payload.SyncDeferred)
	return nil
}

func (m *managedNavi) storeFunnelPayload(payload *managedNaviFunnelPayload) {
	normalized := managedNaviFunnelPayload{}
	if payload != nil {
		normalized = *payload
	}
	if normalized.Services == nil {
		normalized.Services = []managedNaviFunnelSyncRecord{}
	}
	if normalized.ServiceCount != len(normalized.Services) {
		normalized.ServiceCount = len(normalized.Services)
	}

	m.mu.Lock()
	m.funnel = normalized
	m.mu.Unlock()
}

func (m *managedNavi) funnelSnapshot() managedNaviFunnelPayload {
	m.mu.RLock()
	defer m.mu.RUnlock()
	snapshot := m.funnel
	if snapshot.Services == nil {
		snapshot.Services = []managedNaviFunnelSyncRecord{}
	}
	return snapshot
}
