package sdk

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"

	rmTesting "github.com/brigadecore/brigade/sdk/v3/internal/restmachinery/testing" // nolint: lll
	"github.com/brigadecore/brigade/sdk/v3/meta"
	metaTesting "github.com/brigadecore/brigade/sdk/v3/meta/testing"
	"github.com/stretchr/testify/require"
)

func TestProjectMarshalJSON(t *testing.T) {
	metaTesting.RequireAPIVersionAndType(t, Project{}, ProjectKind)
}

func TestProjectListMarshalJSON(t *testing.T) {
	metaTesting.RequireAPIVersionAndType(t, ProjectList{}, "ProjectList")
}

func TestNewProjectsClient(t *testing.T) {
	client, ok := NewProjectsClient(
		rmTesting.TestAPIAddress,
		rmTesting.TestAPIToken,
		nil,
	).(*projectsClient)
	require.True(t, ok)
	rmTesting.RequireBaseClient(t, client.BaseClient)
	require.NotNil(t, client.authzClient)
	require.Equal(t, client.authzClient, client.Authz())
	require.NotNil(t, client.secretsClient)
	require.Equal(t, client.secretsClient, client.Secrets())
}

func TestProjectsClientCreate(t *testing.T) {
	testProject := Project{
		ObjectMeta: meta.ObjectMeta{
			ID: "bluebook",
		},
	}
	server := httptest.NewServer(
		http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()
				require.Equal(t, http.MethodPost, r.Method)
				require.Equal(t, "/v2/projects", r.URL.Path)
				bodyBytes, err := ioutil.ReadAll(r.Body)
				require.NoError(t, err)
				project := Project{}
				err = json.Unmarshal(bodyBytes, &project)
				require.NoError(t, err)
				require.Equal(t, testProject, project)
				w.WriteHeader(http.StatusCreated)
				fmt.Fprintln(w, string(bodyBytes))
			},
		),
	)
	defer server.Close()
	client := NewProjectsClient(server.URL, rmTesting.TestAPIToken, nil)
	project, err := client.Create(
		context.Background(),
		testProject,
		nil,
	)
	require.NoError(t, err)
	require.Equal(t, testProject, project)
}

func TestProjectsClientCreateFromBytes(t *testing.T) {
	testProject := Project{
		ObjectMeta: meta.ObjectMeta{
			ID: "bluebook",
		},
	}
	testProjectBytes, err := json.Marshal(testProject)
	require.NoError(t, err)
	server := httptest.NewServer(
		http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()
				require.Equal(t, http.MethodPost, r.Method)
				require.Equal(t, "/v2/projects", r.URL.Path)
				var bodyBytes []byte
				bodyBytes, err = ioutil.ReadAll(r.Body)
				require.NoError(t, err)
				require.Equal(t, testProjectBytes, bodyBytes)
				w.WriteHeader(http.StatusCreated)
				fmt.Fprintln(w, string(bodyBytes))
			},
		),
	)
	defer server.Close()
	client := NewProjectsClient(server.URL, rmTesting.TestAPIToken, nil)
	project, err :=
		client.CreateFromBytes(context.Background(), testProjectBytes, nil)
	require.NoError(t, err)
	require.Equal(t, testProject, project)
}

func TestProjectsClientList(t *testing.T) {
	testProjects := ProjectList{
		Items: []Project{
			{
				ObjectMeta: meta.ObjectMeta{
					ID: "bluebook",
				},
			},
		},
	}
	server := httptest.NewServer(
		http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				require.Equal(t, http.MethodGet, r.Method)
				require.Equal(t, "/v2/projects", r.URL.Path)
				bodyBytes, err := json.Marshal(testProjects)
				require.NoError(t, err)
				w.WriteHeader(http.StatusOK)
				fmt.Fprintln(w, string(bodyBytes))
			},
		),
	)
	defer server.Close()
	client := NewProjectsClient(server.URL, rmTesting.TestAPIToken, nil)
	projects, err := client.List(context.Background(), nil, nil)
	require.NoError(t, err)
	require.Equal(t, testProjects, projects)
}

func TestProjectsClientGet(t *testing.T) {
	testProject := Project{
		ObjectMeta: meta.ObjectMeta{
			ID: "bluebook",
		},
	}
	server := httptest.NewServer(
		http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				require.Equal(t, http.MethodGet, r.Method)
				require.Equal(
					t,
					fmt.Sprintf("/v2/projects/%s", testProject.ID),
					r.URL.Path,
				)
				bodyBytes, err := json.Marshal(testProject)
				require.NoError(t, err)
				w.WriteHeader(http.StatusOK)
				fmt.Fprintln(w, string(bodyBytes))
			},
		),
	)
	defer server.Close()
	client := NewProjectsClient(server.URL, rmTesting.TestAPIToken, nil)
	project, err := client.Get(context.Background(), testProject.ID, nil)
	require.NoError(t, err)
	require.Equal(t, testProject, project)
}

func TestProjectsClientUpdate(t *testing.T) {
	testProject := Project{
		ObjectMeta: meta.ObjectMeta{
			ID: "bluebook",
		},
	}
	testOpts := ProjectUpdateOptions{
		CreateIfNotFound: true,
	}
	server := httptest.NewServer(
		http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()
				require.Equal(t, http.MethodPut, r.Method)
				require.Equal(
					t,
					fmt.Sprintf("/v2/projects/%s", testProject.ID),
					r.URL.Path,
				)
				require.Equal(
					t,
					strconv.FormatBool(testOpts.CreateIfNotFound),
					r.URL.Query().Get("create"),
				)
				bodyBytes, err := ioutil.ReadAll(r.Body)
				require.NoError(t, err)
				project := Project{}
				err = json.Unmarshal(bodyBytes, &project)
				require.NoError(t, err)
				require.Equal(t, testProject, project)
				w.WriteHeader(http.StatusOK)
				fmt.Fprintln(w, string(bodyBytes))
			},
		),
	)
	defer server.Close()
	client := NewProjectsClient(server.URL, rmTesting.TestAPIToken, nil)
	project, err := client.Update(context.Background(), testProject, &testOpts)
	require.NoError(t, err)
	require.Equal(t, testProject, project)
}

func TestProjectsClientUpdateFromBytes(t *testing.T) {
	testProject := Project{
		ObjectMeta: meta.ObjectMeta{
			ID: "bluebook",
		},
	}
	testProjectBytes, err := json.Marshal(testProject)
	require.NoError(t, err)
	server := httptest.NewServer(
		http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				defer r.Body.Close()
				require.Equal(t, http.MethodPut, r.Method)
				require.Equal(
					t,
					fmt.Sprintf("/v2/projects/%s", testProject.ID),
					r.URL.Path,
				)
				var bodyBytes []byte
				bodyBytes, err = ioutil.ReadAll(r.Body)
				require.NoError(t, err)
				require.Equal(t, testProjectBytes, bodyBytes)
				w.WriteHeader(http.StatusOK)
				fmt.Fprintln(w, string(bodyBytes))
			},
		),
	)
	defer server.Close()
	client := NewProjectsClient(server.URL, rmTesting.TestAPIToken, nil)
	project, err := client.UpdateFromBytes(
		context.Background(),
		testProject.ID,
		testProjectBytes,
		nil,
	)
	require.NoError(t, err)
	require.Equal(t, testProject, project)
}

func TestProjectsClientDelete(t *testing.T) {
	const testProjectID = "bluebook"
	server := httptest.NewServer(
		http.HandlerFunc(
			func(w http.ResponseWriter, r *http.Request) {
				require.Equal(t, http.MethodDelete, r.Method)
				require.Equal(
					t,
					fmt.Sprintf("/v2/projects/%s", testProjectID),
					r.URL.Path,
				)
				fmt.Fprintln(w, "{}")
			},
		),
	)
	defer server.Close()
	client := NewProjectsClient(server.URL, rmTesting.TestAPIToken, nil)
	err := client.Delete(context.Background(), testProjectID, nil)
	require.NoError(t, err)
}
