// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

package middleware

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/go-openapi/runtime"
	"github.com/go-openapi/strfmt"
	"github.com/go-openapi/testify/v2/assert"
	"github.com/go-openapi/testify/v2/require"
)

func TestUntypedFormPost(t *testing.T) {
	params := parametersForFormUpload()
	binder := NewUntypedRequestBinder(params, nil, strfmt.Default)

	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, bytes.NewBufferString(`name=the-name&age=32`))
	require.NoError(t, err)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	data := make(map[string]any)
	require.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
	assert.Equal(t, "the-name", data[paramKeyName])
	assert.EqualValues(t, 32, data[paramKeyAge])

	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, bytes.NewBufferString(`name=%3&age=32`))
	require.NoError(t, err)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	data = make(map[string]any)
	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
}

func TestUntypedFileUpload(t *testing.T) {
	binder := paramsForFileUpload()

	body := bytes.NewBuffer(nil)
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile("file", "plain-jane.txt")
	require.NoError(t, err)

	_, err = part.Write([]byte("the file contents"))
	require.NoError(t, err)
	require.NoError(t, writer.WriteField(paramKeyName, "the-name"))
	require.NoError(t, writer.Close())

	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, body)
	require.NoError(t, err)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	data := make(map[string]any)
	require.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
	assert.Equal(t, "the-name", data[paramKeyName])
	assert.NotNil(t, data["file"])
	assert.IsType(t, runtime.File{}, data["file"])
	file, ok := data["file"].(runtime.File)
	require.TrueT(t, ok)
	require.NotNil(t, file.Header)
	assert.EqualT(t, "plain-jane.txt", file.Header.Filename)

	bb, err := io.ReadAll(file.Data)
	require.NoError(t, err)
	assert.Equal(t, []byte("the file contents"), bb)

	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, body)
	require.NoError(t, err)
	req.Header.Set("Content-Type", jsonMime)
	data = make(map[string]any)
	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))

	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, body)
	require.NoError(t, err)
	req.Header.Set("Content-Type", "application(")
	data = make(map[string]any)
	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))

	body = bytes.NewBuffer(nil)
	writer = multipart.NewWriter(body)
	part, err = writer.CreateFormFile("bad-name", "plain-jane.txt")
	require.NoError(t, err)

	_, err = part.Write([]byte("the file contents"))
	require.NoError(t, err)
	require.NoError(t, writer.WriteField(paramKeyName, "the-name"))
	require.NoError(t, writer.Close())
	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, body)
	require.NoError(t, err)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	data = make(map[string]any)
	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))

	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, body)
	require.NoError(t, err)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	_, err = req.MultipartReader()
	require.NoError(t, err)

	data = make(map[string]any)
	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))

	writer = multipart.NewWriter(body)
	require.NoError(t, writer.WriteField(paramKeyName, "the-name"))
	require.NoError(t, writer.Close())

	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, body)
	require.NoError(t, err)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	data = make(map[string]any)
	require.Error(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
}

func TestUntypedOptionalFileUpload(t *testing.T) {
	binder := paramsForOptionalFileUpload()

	body := bytes.NewBuffer(nil)
	writer := multipart.NewWriter(body)
	require.NoError(t, writer.WriteField(paramKeyName, "the-name"))
	require.NoError(t, writer.Close())

	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, body)
	require.NoError(t, err)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	data := make(map[string]any)
	require.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
	assert.Equal(t, "the-name", data[paramKeyName])

	writer = multipart.NewWriter(body)
	part, err := writer.CreateFormFile("file", "plain-jane.txt")
	require.NoError(t, err)
	_, err = part.Write([]byte("the file contents"))
	require.NoError(t, err)
	require.NoError(t, writer.WriteField(paramKeyName, "the-name"))
	require.NoError(t, writer.Close())

	req, err = http.NewRequestWithContext(context.Background(), http.MethodPost, testURL, body)
	require.NoError(t, err)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	require.NoError(t, writer.Close())

	data = make(map[string]any)
	require.NoError(t, binder.Bind(req, nil, runtime.JSONConsumer(), &data))
	assert.Equal(t, "the-name", data[paramKeyName])
	assert.NotNil(t, data["file"])
	assert.IsType(t, runtime.File{}, data["file"])
	file, ok := data["file"].(runtime.File)
	require.TrueT(t, ok)
	assert.NotNil(t, file.Header)
	assert.EqualT(t, "plain-jane.txt", file.Header.Filename)

}

func TestUntypedBindingTypesForValid(t *testing.T) {
	op2 := parametersForAllTypes("")
	binder := NewUntypedRequestBinder(op2, nil, strfmt.Default)

	confirmed := true
	name := "thomas"
	friend := map[string]any{paramKeyName: valToby, paramKeyAge: json.Number("32")}
	id, age, score, factor := int64(7575), int32(348), float32(5.309), float64(37.403)
	requestID := 19394858
	tags := []string{tagOne, tagTwo, tagThree}
	dt1 := time.Date(2014, 8, 9, 0, 0, 0, 0, time.UTC)
	planned := strfmt.Date(dt1)
	dt2 := time.Date(2014, 10, 12, 8, 5, 5, 0, time.UTC)
	delivered := strfmt.DateTime(dt2)
	picture := base64.URLEncoding.EncodeToString([]byte("hello"))
	uri, err := url.Parse("http://localhost:8002/hello/7575")
	require.NoError(t, err)
	qs := uri.Query()
	qs.Add(paramKeyName, name)
	qs.Add("confirmed", "true")
	qs.Add(paramKeyAge, "348")
	qs.Add("score", "5.309")
	qs.Add("factor", "37.403")
	qs.Add("tags", strings.Join(tags, ","))
	qs.Add("planned", planned.String())
	qs.Add("delivered", delivered.String())
	qs.Add("picture", picture)

	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, uri.String()+"?"+qs.Encode(), bytes.NewBufferString(`{"name":"toby","age":32}`))
	require.NoError(t, err)
	req.Header.Set("Content-Type", jsonMime)
	req.Header.Set("X-Request-Id", "19394858")

	data := make(map[string]any)
	err = binder.Bind(req, RouteParams([]RouteParam{{paramKeyID, "7575"}}), runtime.JSONConsumer(), &data)
	require.NoError(t, err)
	assert.Equal(t, id, data[paramKeyID])
	assert.Equal(t, name, data[paramKeyName])
	assert.Equal(t, friend, data["friend"])
	assert.EqualValues(t, requestID, data["X-Request-Id"])
	assert.Equal(t, tags, data["tags"])
	assert.Equal(t, planned, data["planned"])
	assert.Equal(t, delivered, data["delivered"])
	assert.Equal(t, confirmed, data["confirmed"])
	assert.Equal(t, age, data[paramKeyAge])
	assert.InDelta(t, factor, data["factor"], 1e-6)
	assert.InDelta(t, score, data["score"], 1e-6)
	pb, err := base64.URLEncoding.DecodeString(picture)
	require.NoError(t, err)
	formatted, ok := data["picture"].(strfmt.Base64)
	require.TrueT(t, ok)
	assert.EqualValues(t, pb, formatted)
}
