package routers_test

import (
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/getkin/kin-openapi/openapi3filter"
	"github.com/getkin/kin-openapi/routers"
	"github.com/getkin/kin-openapi/routers/gorillamux"
	"github.com/getkin/kin-openapi/routers/legacy"
)

func TestIssue356(t *testing.T) {
	spec := func(servers string) []byte {
		return []byte(`
openapi: 3.0.0
info:
  title: Example
  version: '1.0'
  description: test
servers:
` + servers + `
paths:
  /test:
    post:
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema: {type: object}
      requestBody:
        content:
          application/json:
            schema: {type: object}
        description: ''
      description: Create a test object
`)
	}

	for servers, expectError := range map[string]bool{
		`
- url: http://localhost:3000/base
- url: /base
`: false,

		`
- url: /base
- url: http://localhost:3000/base
`: false,

		`- url: /base`: false,

		`- url: http://localhost:3000/base`: true,

		``: true,
	} {
		loader := &openapi3.Loader{Context: t.Context()}
		t.Logf("using servers: %q (%v)", servers, expectError)
		doc, err := loader.LoadFromData(spec(servers))
		require.NoError(t, err)
		err = doc.Validate(t.Context())
		require.NoError(t, err)
		gorillamuxNewRouterWrapped := func(doc *openapi3.T, opts ...openapi3.ValidationOption) (routers.Router, error) {
			return gorillamux.NewRouter(doc)
		}

		for i, newRouter := range []func(*openapi3.T, ...openapi3.ValidationOption) (routers.Router, error){gorillamuxNewRouterWrapped, legacy.NewRouter} {
			t.Logf("using NewRouter from %s", map[int]string{0: "gorillamux", 1: "legacy"}[i])
			router, err := newRouter(doc)
			require.NoError(t, err)

			if true {
				t.Logf("using naked newRouter")
				httpReq, err := http.NewRequest(http.MethodPost, "/base/test", strings.NewReader(`{}`))
				require.NoError(t, err)
				httpReq.Header.Set("Content-Type", "application/json")

				route, pathParams, err := router.FindRoute(httpReq)
				if expectError {
					require.Error(t, err, routers.ErrPathNotFound)
					return
				}
				require.NoError(t, err)

				requestValidationInput := &openapi3filter.RequestValidationInput{
					Request:    httpReq,
					PathParams: pathParams,
					Route:      route,
				}
				err = openapi3filter.ValidateRequest(t.Context(), requestValidationInput)
				require.NoError(t, err)
			}

			if true {
				t.Logf("using httptest.NewServer")
				ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					route, pathParams, err := router.FindRoute(r)
					if err != nil {
						w.WriteHeader(http.StatusInternalServerError)
						_, err := w.Write([]byte(err.Error()))
						require.NoError(t, err)
						return
					}

					requestValidationInput := &openapi3filter.RequestValidationInput{
						Request:    r,
						PathParams: pathParams,
						Route:      route,
					}
					err = openapi3filter.ValidateRequest(r.Context(), requestValidationInput)
					require.NoError(t, err)

					w.Header().Set("Content-Type", "application/json")
					_, err = w.Write([]byte("{}"))
					require.NoError(t, err)
				}))
				defer ts.Close()

				req, err := http.NewRequest(http.MethodPost, ts.URL+"/base/test", strings.NewReader(`{}`))
				require.NoError(t, err)
				req.Header.Set("Content-Type", "application/json")

				rep, err := http.DefaultClient.Do(req)
				require.NoError(t, err)
				defer rep.Body.Close()
				body, err := io.ReadAll(rep.Body)
				require.NoError(t, err)

				if expectError {
					require.Equal(t, 500, rep.StatusCode)
					require.Equal(t, routers.ErrPathNotFound.Error(), string(body))
					return
				}
				require.Equal(t, 200, rep.StatusCode)
				require.Equal(t, "{}", string(body))
			}
		}
	}
}
