package rest_api_test

// Docs:
// - Endpoint: https://developer.salesforce.com/docs/atlas.en-us.254.0.api_rest.meta/api_rest/resources_async_specifications_oas3.htm
// - Errors: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest.htm

import (
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/nexuscrm/tests/contract/internal/contracttest"
	"github.com/nexuscrm/tests/contract/internal/liveapi"
	"github.com/stretchr/testify/require"
)

func TestAsyncOpenAPISpecificationsOAS3(t *testing.T) {
	type requestResp struct {
		Href string `json:"href"`
	}
	type schemaResp struct {
		OpenAPI string `json:"openapi"`
		Info    struct {
			Title   string `json:"title"`
			Version string `json:"version"`
		} `json:"info"`
		Paths map[string]any `json:"paths"`
	}

	requestSpec := func(t *testing.T) (string, bool) {
		t.Helper()

		client := liveapi.NewAuthenticatedClient(t)
		resp, err := client.DoJSON(http.MethodPost, "/async/specifications/oas3", map[string]any{})
		require.NoError(t, err)

		switch resp.StatusCode {
		case http.StatusAccepted:
			contracttest.AssertJSONContentType(t, resp)
			var body requestResp
			liveapi.DecodeJSON(t, resp, &body)
			require.NotEmpty(t, body.Href)
			return body.Href, true
		case http.StatusNotFound, http.StatusForbidden:
			body := liveapi.ReadBody(t, resp)
			t.Skipf("OpenAPI schema beta endpoint unavailable in this org (status %d body %s)", resp.StatusCode, strings.TrimSpace(string(body)))
			return "", false
		default:
			body := liveapi.ReadBody(t, resp)
			t.Fatalf("unexpected status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
			return "", false
		}
	}

	t.Run("request schema returns href", func(t *testing.T) {
		_, _ = requestSpec(t)
	})

	t.Run("retrieve schema returns OpenAPI document", func(t *testing.T) {
		href, ok := requestSpec(t)
		if !ok {
			return
		}

		// href can be either:
		// - /v63.0/async/specifications/oas3/{schemaId}
		// - /services/data/v63.0/async/specifications/oas3/{schemaId}
		schemaPath := href
		if strings.HasPrefix(href, "/v") && strings.Contains(href, "/async/specifications/oas3/") {
			schemaPath = "/services/data" + href
		}
		require.Contains(t, schemaPath, "/async/specifications/oas3/")

		client := liveapi.NewAuthenticatedClient(t)
		var resp *http.Response
		for attempt := 0; attempt < 6; attempt++ {
			r, err := client.DoJSON(http.MethodGet, schemaPath, nil)
			require.NoError(t, err)
			if r.StatusCode != http.StatusAccepted {
				resp = r
				break
			}
			_ = r.Body.Close()
			time.Sleep(1 * time.Second)
		}
		require.NotNil(t, resp, "schema never became available")

		switch resp.StatusCode {
		case http.StatusOK:
			contracttest.AssertJSONContentType(t, resp)
			var body schemaResp
			liveapi.DecodeJSON(t, resp, &body)
			require.NotEmpty(t, body.OpenAPI)
			require.NotEmpty(t, body.Info.Title)
			require.NotEmpty(t, body.Paths)
		case http.StatusNotFound, http.StatusForbidden:
			body := liveapi.ReadBody(t, resp)
			t.Skipf("OpenAPI schema endpoint unavailable in this org (status %d body %s)", resp.StatusCode, strings.TrimSpace(string(body)))
		default:
			body := liveapi.ReadBody(t, resp)
			t.Fatalf("unexpected status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
		}
	})

	t.Run("missing bearer token is rejected", func(t *testing.T) {
		resp := contracttest.DoAnonymousRequest(t, http.MethodPost, "/services/data/v63.0/async/specifications/oas3", map[string]any{})
		contracttest.AssertSalesforceError(t, resp, http.StatusUnauthorized, "INVALID_SESSION_ID")
	})

	t.Run("invalid schema id is rejected", func(t *testing.T) {
		client := liveapi.NewAuthenticatedClient(t)
		resp, err := client.DoJSON(http.MethodGet, "/async/specifications/oas3/DefinitelyNotASchemaId", nil)
		require.NoError(t, err)

		switch resp.StatusCode {
		case http.StatusNotFound:
			contracttest.AssertSalesforceError(t, resp, http.StatusNotFound, "NOT_FOUND")
		case http.StatusForbidden:
			body := liveapi.ReadBody(t, resp)
			t.Skipf("OpenAPI schema endpoint unavailable in this org (status %d body %s)", resp.StatusCode, strings.TrimSpace(string(body)))
		default:
			body := liveapi.ReadBody(t, resp)
			t.Fatalf("unexpected status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
		}
	})
}
