// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: MPL-2.0

package api

import (
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"testing"
)

func TestListMounts(t *testing.T) {
	mockVaultServer := httptest.NewServer(http.HandlerFunc(mockVaultMountsHandler))
	defer mockVaultServer.Close()

	cfg := DefaultConfig()
	cfg.Address = mockVaultServer.URL
	client, err := NewClient(cfg)
	if err != nil {
		t.Fatal(err)
	}

	resp, err := client.Sys().ListMounts()
	if err != nil {
		t.Fatal(err)
	}

	expectedMounts := map[string]struct {
		Type    string
		Version string
	}{
		"cubbyhole/": {Type: "cubbyhole", Version: "v1.0.0"},
		"identity/":  {Type: "identity", Version: ""},
		"secret/":    {Type: "kv", Version: ""},
		"sys/":       {Type: "system", Version: ""},
	}

	for path, mount := range resp {
		expected, ok := expectedMounts[path]
		if !ok {
			t.Errorf("Unexpected mount: %s: %+v", path, mount)
			continue
		}
		if expected.Type != mount.Type || expected.Version != mount.PluginVersion {
			t.Errorf("Mount did not match: %s -> expected %+v but got %+v", path, expected, mount)
		}
	}

	for path, expected := range expectedMounts {
		mount, ok := resp[path]
		if !ok {
			t.Errorf("Expected mount not found mount: %s: %+v", path, expected)
			continue
		}
		if expected.Type != mount.Type || expected.Version != mount.PluginVersion {
			t.Errorf("Mount did not match: %s -> expected %+v but got %+v", path, expected, mount)
		}
	}
}

func mockVaultMountsHandler(w http.ResponseWriter, _ *http.Request) {
	_, _ = w.Write([]byte(listMountsResponse))
}

const listMountsResponse = `{
  "request_id": "3cd881e9-ea50-2e06-90b2-5641667485fa",
  "lease_id": "",
  "lease_duration": 0,
  "renewable": false,
  "data": {
    "cubbyhole/": {
      "accessor": "cubbyhole_2e3fc28d",
      "config": {
        "default_lease_ttl": 0,
        "force_no_cache": false,
        "max_lease_ttl": 0
      },
      "description": "per-token private secret storage",
      "external_entropy_access": false,
      "local": true,
      "options": null,
      "plugin_version": "v1.0.0",
      "running_sha256": "",
      "running_plugin_version": "",
      "seal_wrap": false,
      "type": "cubbyhole",
      "uuid": "575063dc-5ef8-4487-c842-22c494c19a6f"
    },
    "identity/": {
      "accessor": "identity_6e01c327",
      "config": {
        "default_lease_ttl": 0,
        "force_no_cache": false,
        "max_lease_ttl": 0,
        "passthrough_request_headers": [
          "Authorization"
        ]
      },
      "description": "identity store",
      "external_entropy_access": false,
      "local": false,
      "options": null,
      "plugin_version": "",
      "running_sha256": "",
      "running_plugin_version": "",
      "seal_wrap": false,
      "type": "identity",
      "uuid": "187d7eba-3471-554b-c2d9-1479612c8046"
    },
    "secret/": {
      "accessor": "kv_3e2f282f",
      "config": {
        "default_lease_ttl": 0,
        "force_no_cache": false,
        "max_lease_ttl": 0
      },
      "description": "key/value secret storage",
      "external_entropy_access": false,
      "local": false,
      "options": {
        "version": "2"
      },
      "plugin_version": "",
      "running_sha256": "",
      "running_plugin_version": "",
      "seal_wrap": false,
      "type": "kv",
      "uuid": "13375e0f-876e-7e96-0a3e-076f37b6b69d"
    },
    "sys/": {
      "accessor": "system_93503264",
      "config": {
        "default_lease_ttl": 0,
        "force_no_cache": false,
        "max_lease_ttl": 0,
        "passthrough_request_headers": [
          "Accept"
        ]
      },
      "description": "system endpoints used for control, policy and debugging",
      "external_entropy_access": false,
      "local": false,
      "options": null,
      "plugin_version": "",
      "running_sha256": "",
      "running_plugin_version": "",
      "seal_wrap": true,
      "type": "system",
      "uuid": "1373242d-cc4d-c023-410b-7f336e7ba0a8"
    }
  }
}`

// TestMountUpdateConfigStructFields ensures that the type MountConfigInput (the struct used to setup a new mount) has
// all the same fields as type TuneMountConfigInput (the struct used to tune an existing mount):
func TestMountUpdateConfigStructFields(t *testing.T) {
	tuneStruct := TuneMountConfigInput{}
	initialStruct := MountConfigInput{}

	tuneReflect := reflect.ValueOf(tuneStruct).Type()
	initialReflect := reflect.ValueOf(initialStruct).Type()

	for i := 0; i < tuneReflect.NumField(); i++ {
		tuneField := tuneReflect.Field(i)
		foundMatch := false
		// Now Find the field in initial Reflect
		for j := 0; j < initialReflect.NumField(); j++ {
			initialField := initialReflect.Field(j)
			if tuneField.Name == initialField.Name {
				jsonTuneFieldTag := tuneField.Tag.Get("json")
				jsonTuneFieldName := strings.Split(jsonTuneFieldTag, ",")[0]
				jsonInitialFieldTag := tuneField.Tag.Get("json")
				jsonInitialFieldName := strings.Split(jsonInitialFieldTag, ",")[0]
				if jsonTuneFieldName != jsonInitialFieldName {
					t.Fatalf("TuneMountConfigInput and MountConfigInput struct fields %v do not have same json names: %v, %v", tuneField.Name, jsonTuneFieldName, jsonInitialFieldName)
				}
				foundMatch = true
				break
			}
		}
		if !foundMatch {
			t.Fatalf("Field %s in TuneMountConfigInput not found in MountConfigInput", tuneField.Name)
		}
	}

	if tuneReflect.NumField() != initialReflect.NumField() {
		for i := 0; i < initialReflect.NumField(); i++ {
			initialField := initialReflect.Field(i)
			foundMatch := false
			for j := 0; j < tuneReflect.NumField(); j++ {
				tuneField := tuneReflect.Field(j)
				if tuneField.Name == initialField.Name {
					foundMatch = true
					break
				}
			}
			if !foundMatch {
				t.Fatalf("Field %s in MountConfigInput not found in TuneMountConfigInput", initialField.Name)
			}
		}
		t.Fatalf("Different number of TuneMountConfigInput fields found in MountConfigInput")
	}
}
