//go:build integration

package rest_test

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/netbirdio/netbird/shared/management/client/rest"
	"github.com/netbirdio/netbird/shared/management/http/api"
	"github.com/netbirdio/netbird/shared/management/http/util"
)

var (
	testAgentNetworkProvider = api.AgentNetworkProvider{
		Id:          "ainp_test",
		ProviderId:  "openai_api",
		Name:        "OpenAI",
		UpstreamUrl: "https://api.openai.com",
		Models:      []api.AgentNetworkProviderModel{},
		Enabled:     true,
	}

	testAgentNetworkPolicy = api.AgentNetworkPolicy{
		Id:                     "ainpol_test",
		Name:                   "Engineering → OpenAI",
		Enabled:                true,
		SourceGroups:           []string{"grp-eng"},
		DestinationProviderIds: []string{"ainp_test"},
	}

	testAgentNetworkGuardrail = api.AgentNetworkGuardrail{
		Id:   "aingr_test",
		Name: "No secrets",
	}

	testAgentNetworkBudgetRule = api.AgentNetworkBudgetRule{
		Id:      "ainbud_test",
		Name:    "Org monthly ceiling",
		Enabled: true,
	}

	testAgentNetworkSettings = api.AgentNetworkSettings{
		Cluster:                "eu.proxy.netbird.io",
		Subdomain:              "violet",
		Endpoint:               "violet.eu.proxy.netbird.io",
		EnableLogCollection:    true,
		AccessLogRetentionDays: ptr(30),
	}
)

func TestAgentNetwork_ListCatalogProviders_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/catalog/providers", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal([]api.AgentNetworkCatalogProvider{{Id: "openai_api", Name: "OpenAI"}})
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.ListCatalogProviders(context.Background())
		require.NoError(t, err)
		assert.Len(t, ret, 1)
		assert.Equal(t, "openai_api", ret[0].Id)
	})
}

func TestAgentNetwork_ListProviders_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/providers", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal([]api.AgentNetworkProvider{testAgentNetworkProvider})
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.ListProviders(context.Background())
		require.NoError(t, err)
		assert.Len(t, ret, 1)
		assert.Equal(t, testAgentNetworkProvider, ret[0])
	})
}

func TestAgentNetwork_GetProvider_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/providers/ainp_test", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "GET", r.Method)
			retBytes, _ := json.Marshal(testAgentNetworkProvider)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.GetProvider(context.Background(), "ainp_test")
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkProvider, *ret)
	})
}

func TestAgentNetwork_GetProvider_Err(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/providers/ainp_test", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal(util.ErrorResponse{Message: "not found", Code: 404})
			w.WriteHeader(404)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		_, err := c.AgentNetwork.GetProvider(context.Background(), "ainp_test")
		require.Error(t, err)
		assert.True(t, rest.IsNotFound(err), "a 404 must be matchable via IsNotFound")
	})
}

func TestAgentNetwork_CreateProvider_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/providers", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "POST", r.Method)
			reqBytes, err := io.ReadAll(r.Body)
			require.NoError(t, err)
			var req api.PostApiAgentNetworkProvidersJSONRequestBody
			require.NoError(t, json.Unmarshal(reqBytes, &req))
			assert.Equal(t, "OpenAI", req.Name)
			require.NotNil(t, req.BootstrapCluster)
			assert.Equal(t, "eu.proxy.netbird.io", *req.BootstrapCluster)
			retBytes, _ := json.Marshal(testAgentNetworkProvider)
			_, err = w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.CreateProvider(context.Background(), api.PostApiAgentNetworkProvidersJSONRequestBody{
			ProviderId:       "openai_api",
			Name:             "OpenAI",
			UpstreamUrl:      "https://api.openai.com",
			ApiKey:           ptr("sk-test"),
			BootstrapCluster: ptr("eu.proxy.netbird.io"),
		})
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkProvider, *ret)
	})
}

func TestAgentNetwork_UpdateProvider_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/providers/ainp_test", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "PUT", r.Method)
			reqBytes, err := io.ReadAll(r.Body)
			require.NoError(t, err)
			// Omitted optional fields must be absent from the wire (not
			// zero-valued) so the server-side merge preserves them.
			assert.NotContains(t, string(reqBytes), "api_key")
			assert.NotContains(t, string(reqBytes), "models")
			retBytes, _ := json.Marshal(testAgentNetworkProvider)
			_, err = w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.UpdateProvider(context.Background(), "ainp_test", api.PutApiAgentNetworkProvidersProviderIdJSONRequestBody{
			ProviderId:  "openai_api",
			Name:        "OpenAI",
			UpstreamUrl: "https://api.openai.com",
		})
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkProvider, *ret)
	})
}

func TestAgentNetwork_DeleteProvider_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/providers/ainp_test", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "DELETE", r.Method)
			_, err := w.Write([]byte("{}"))
			require.NoError(t, err)
		})
		err := c.AgentNetwork.DeleteProvider(context.Background(), "ainp_test")
		require.NoError(t, err)
	})
}

func TestAgentNetwork_ListPolicies_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/policies", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal([]api.AgentNetworkPolicy{testAgentNetworkPolicy})
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.ListPolicies(context.Background())
		require.NoError(t, err)
		assert.Len(t, ret, 1)
		assert.Equal(t, testAgentNetworkPolicy, ret[0])
	})
}

func TestAgentNetwork_GetPolicy_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/policies/ainpol_test", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal(testAgentNetworkPolicy)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.GetPolicy(context.Background(), "ainpol_test")
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkPolicy, *ret)
	})
}

func TestAgentNetwork_CreatePolicy_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/policies", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "POST", r.Method)
			retBytes, _ := json.Marshal(testAgentNetworkPolicy)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.CreatePolicy(context.Background(), api.PostApiAgentNetworkPoliciesJSONRequestBody{
			Name:                   "Engineering → OpenAI",
			SourceGroups:           []string{"grp-eng"},
			DestinationProviderIds: []string{"ainp_test"},
		})
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkPolicy, *ret)
	})
}

func TestAgentNetwork_UpdatePolicy_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/policies/ainpol_test", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "PUT", r.Method)
			retBytes, _ := json.Marshal(testAgentNetworkPolicy)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.UpdatePolicy(context.Background(), "ainpol_test", api.PutApiAgentNetworkPoliciesPolicyIdJSONRequestBody{
			Name:                   "Engineering → OpenAI",
			SourceGroups:           []string{"grp-eng"},
			DestinationProviderIds: []string{"ainp_test"},
		})
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkPolicy, *ret)
	})
}

func TestAgentNetwork_DeletePolicy_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/policies/ainpol_test", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "DELETE", r.Method)
			_, err := w.Write([]byte("{}"))
			require.NoError(t, err)
		})
		err := c.AgentNetwork.DeletePolicy(context.Background(), "ainpol_test")
		require.NoError(t, err)
	})
}

func TestAgentNetwork_ListGuardrails_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/guardrails", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal([]api.AgentNetworkGuardrail{testAgentNetworkGuardrail})
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.ListGuardrails(context.Background())
		require.NoError(t, err)
		assert.Len(t, ret, 1)
		assert.Equal(t, testAgentNetworkGuardrail, ret[0])
	})
}

func TestAgentNetwork_GetGuardrail_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/guardrails/aingr_test", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal(testAgentNetworkGuardrail)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.GetGuardrail(context.Background(), "aingr_test")
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkGuardrail, *ret)
	})
}

func TestAgentNetwork_CreateGuardrail_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/guardrails", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "POST", r.Method)
			retBytes, _ := json.Marshal(testAgentNetworkGuardrail)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.CreateGuardrail(context.Background(), api.PostApiAgentNetworkGuardrailsJSONRequestBody{
			Name: "No secrets",
		})
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkGuardrail, *ret)
	})
}

func TestAgentNetwork_UpdateGuardrail_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/guardrails/aingr_test", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "PUT", r.Method)
			retBytes, _ := json.Marshal(testAgentNetworkGuardrail)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.UpdateGuardrail(context.Background(), "aingr_test", api.PutApiAgentNetworkGuardrailsGuardrailIdJSONRequestBody{
			Name: "No secrets",
		})
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkGuardrail, *ret)
	})
}

func TestAgentNetwork_DeleteGuardrail_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/guardrails/aingr_test", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "DELETE", r.Method)
			_, err := w.Write([]byte("{}"))
			require.NoError(t, err)
		})
		err := c.AgentNetwork.DeleteGuardrail(context.Background(), "aingr_test")
		require.NoError(t, err)
	})
}

func TestAgentNetwork_ListBudgetRules_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/budget-rules", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal([]api.AgentNetworkBudgetRule{testAgentNetworkBudgetRule})
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.ListBudgetRules(context.Background())
		require.NoError(t, err)
		assert.Len(t, ret, 1)
		assert.Equal(t, testAgentNetworkBudgetRule, ret[0])
	})
}

func TestAgentNetwork_GetBudgetRule_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/budget-rules/ainbud_test", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal(testAgentNetworkBudgetRule)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.GetBudgetRule(context.Background(), "ainbud_test")
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkBudgetRule, *ret)
	})
}

func TestAgentNetwork_CreateBudgetRule_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/budget-rules", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "POST", r.Method)
			retBytes, _ := json.Marshal(testAgentNetworkBudgetRule)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.CreateBudgetRule(context.Background(), api.PostApiAgentNetworkBudgetRulesJSONRequestBody{
			Name: "Org monthly ceiling",
		})
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkBudgetRule, *ret)
	})
}

func TestAgentNetwork_UpdateBudgetRule_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/budget-rules/ainbud_test", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "PUT", r.Method)
			retBytes, _ := json.Marshal(testAgentNetworkBudgetRule)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.UpdateBudgetRule(context.Background(), "ainbud_test", api.PutApiAgentNetworkBudgetRulesRuleIdJSONRequestBody{
			Name: "Org monthly ceiling",
		})
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkBudgetRule, *ret)
	})
}

func TestAgentNetwork_DeleteBudgetRule_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/budget-rules/ainbud_test", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "DELETE", r.Method)
			_, err := w.Write([]byte("{}"))
			require.NoError(t, err)
		})
		err := c.AgentNetwork.DeleteBudgetRule(context.Background(), "ainbud_test")
		require.NoError(t, err)
	})
}

func TestAgentNetwork_GetSettings_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal(testAgentNetworkSettings)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.GetSettings(context.Background())
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkSettings, *ret)
	})
}

// TestAgentNetwork_GetSettings_UnbootstrappedDefaults pins the settings-read
// contract: an unbootstrapped account answers 200 with the defaults and empty
// cluster/subdomain/endpoint, which the client passes through untouched.
func TestAgentNetwork_GetSettings_UnbootstrappedDefaults(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal(api.AgentNetworkSettings{
				EnableLogCollection:    true,
				AccessLogRetentionDays: ptr(30),
			})
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.GetSettings(context.Background())
		require.NoError(t, err)
		assert.Empty(t, ret.Endpoint, "empty endpoint is the not-bootstrapped signal")
		assert.True(t, ret.EnableLogCollection, "defaults must pass through")
	})
}

func TestAgentNetwork_GetSettings_Err(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal(util.ErrorResponse{Message: "no", Code: 403})
			w.WriteHeader(403)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		_, err := c.AgentNetwork.GetSettings(context.Background())
		require.Error(t, err)
		assert.Equal(t, "no", err.Error())
	})
}

// TestAgentNetwork_GetSettings_LegacyNullBody pins the compatibility shim for
// management servers that answered 200 with a JSON null body before the
// defaults contract: the client translates that shape into an IsNotFound
// error instead of returning a bogus zero-valued settings object or
// fabricating defaults the server never stated.
func TestAgentNetwork_GetSettings_LegacyNullBody(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
			_, err := w.Write([]byte("null"))
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.GetSettings(context.Background())
		require.Error(t, err)
		assert.Nil(t, ret)
		assert.True(t, rest.IsNotFound(err), "the legacy 200+null shape must surface as IsNotFound")
	})
}

func TestAgentNetwork_UpdateSettings_200(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
			assert.Equal(t, "PUT", r.Method)
			reqBytes, err := io.ReadAll(r.Body)
			require.NoError(t, err)
			var req api.PutApiAgentNetworkSettingsJSONRequestBody
			require.NoError(t, json.Unmarshal(reqBytes, &req))
			require.NotNil(t, req.Cluster, "bootstrap cluster must be on the wire")
			assert.Equal(t, "eu.proxy.netbird.io", *req.Cluster)
			assert.True(t, req.EnableLogCollection)
			retBytes, _ := json.Marshal(testAgentNetworkSettings)
			_, err = w.Write(retBytes)
			require.NoError(t, err)
		})
		ret, err := c.AgentNetwork.UpdateSettings(context.Background(), api.PutApiAgentNetworkSettingsJSONRequestBody{
			Cluster:             ptr("eu.proxy.netbird.io"),
			EnableLogCollection: true,
		})
		require.NoError(t, err)
		assert.Equal(t, testAgentNetworkSettings, *ret)
	})
}

func TestAgentNetwork_UpdateSettings_Err(t *testing.T) {
	withMockClient(func(c *rest.Client, mux *http.ServeMux) {
		mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
			retBytes, _ := json.Marshal(util.ErrorResponse{Message: "cluster is immutable once assigned (current: eu.proxy.netbird.io)", Code: 422})
			w.WriteHeader(422)
			_, err := w.Write(retBytes)
			require.NoError(t, err)
		})
		_, err := c.AgentNetwork.UpdateSettings(context.Background(), api.PutApiAgentNetworkSettingsJSONRequestBody{
			Cluster: ptr("us.proxy.netbird.io"),
		})
		require.Error(t, err)
		assert.Contains(t, err.Error(), "immutable")
	})
}
