package approle

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	vault "github.com/hashicorp/vault/api"
)

func testCAHelper(t *testing.T) (*url.URL, *vault.Client) {
	t.Helper()

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.RequestURI {
		case "/v1/auth/approle/login":
			w.WriteHeader(http.StatusOK)
			fmt.Fprintf(w, `{
				  "auth": {
					"client_token": "hvs.0000"
				  }
				}`)
		case "/v1/auth/custom-approle/login":
			w.WriteHeader(http.StatusOK)
			fmt.Fprintf(w, `{
				  "auth": {
					"client_token": "hvs.9999"
				  }
				}`)
		default:
			w.WriteHeader(http.StatusNotFound)
			fmt.Fprintf(w, `{"error":"not found"}`)
		}
	}))
	t.Cleanup(func() {
		srv.Close()
	})
	u, err := url.Parse(srv.URL)
	if err != nil {
		srv.Close()
		t.Fatal(err)
	}

	config := vault.DefaultConfig()
	config.Address = srv.URL

	client, err := vault.NewClient(config)
	if err != nil {
		srv.Close()
		t.Fatal(err)
	}

	return u, client
}

func TestApprole_LoginMountPaths(t *testing.T) {
	caURL, _ := testCAHelper(t)

	config := vault.DefaultConfig()
	config.Address = caURL.String()
	client, _ := vault.NewClient(config)

	tests := []struct {
		name      string
		mountPath string
		token     string
	}{
		{
			name:      "ok default mount path",
			mountPath: "",
			token:     "hvs.0000",
		},
		{
			name:      "ok explicit mount path",
			mountPath: "approle",
			token:     "hvs.0000",
		},
		{
			name:      "ok custom mount path",
			mountPath: "custom-approle",
			token:     "hvs.9999",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			method, err := NewApproleAuthMethod(tt.mountPath, json.RawMessage(`{"RoleID":"roleID","SecretID":"secretID","IsWrappingToken":false}`))
			if err != nil {
				t.Errorf("NewApproleAuthMethod() error = %v", err)
				return
			}

			secret, err := client.Auth().Login(context.Background(), method)
			if err != nil {
				t.Errorf("Login() error = %v", err)
				return
			}

			token, _ := secret.TokenID()
			if token != tt.token {
				t.Errorf("Token error got %v, expected %v", token, tt.token)
				return
			}
		})
	}
}

func TestApprole_NewApproleAuthMethod(t *testing.T) {
	tests := []struct {
		name      string
		mountPath string
		raw       string
		wantErr   bool
	}{
		{
			"ok secret-id string",
			"",
			`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000"}`,
			false,
		},
		{
			"ok secret-id string and wrapped",
			"",
			`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "isWrappedToken": true}`,
			false,
		},
		{
			"ok secret-id string and wrapped with custom mountPath",
			"approle2",
			`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "isWrappedToken": true}`,
			false,
		},
		{
			"ok secret-id file",
			"",
			`{"RoleID": "0000-0000-0000-0000", "SecretIDFile": "./secret-id"}`,
			false,
		},
		{
			"ok secret-id env",
			"",
			`{"RoleID": "0000-0000-0000-0000", "SecretIDEnv": "VAULT_APPROLE_SECRETID"}`,
			false,
		},
		{
			"fail mandatory role-id",
			"",
			`{}`,
			true,
		},
		{
			"fail mandatory secret-id any",
			"",
			`{"RoleID": "0000-0000-0000-0000"}`,
			true,
		},
		{
			"fail multiple secret-id types id and env",
			"",
			`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "SecretIDEnv": "VAULT_APPROLE_SECRETID"}`,
			true,
		},
		{
			"fail multiple secret-id types id and file",
			"",
			`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "SecretIDFile": "./secret-id"}`,
			true,
		},
		{
			"fail multiple secret-id types env and file",
			"",
			`{"RoleID": "0000-0000-0000-0000", "SecretIDFile": "./secret-id", "SecretIDEnv": "VAULT_APPROLE_SECRETID"}`,
			true,
		},
		{
			"fail multiple secret-id types all",
			"",
			`{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "SecretIDFile": "./secret-id", "SecretIDEnv": "VAULT_APPROLE_SECRETID"}`,
			true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := NewApproleAuthMethod(tt.mountPath, json.RawMessage(tt.raw))
			if (err != nil) != tt.wantErr {
				t.Errorf("Approle.NewApproleAuthMethod() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
		})
	}
}
