package requestdemo

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

type input struct {
	Count *int `json:"count"`
}

func jsonHandler(w http.ResponseWriter, r *http.Request) {
	media, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
	if err != nil || media != "application/json" {
		http.Error(w, "JSON required", 415)
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, 128)
	defer r.Body.Close()
	d := json.NewDecoder(r.Body)
	d.DisallowUnknownFields()
	var v input
	err = d.Decode(&v)
	if err == nil {
		var extra any
		err = d.Decode(&extra)
		if err == io.EOF {
			err = nil
		} else if err == nil {
			err = errors.New("multiple JSON values")
		}
	}
	if err != nil {
		var tooLarge *http.MaxBytesError
		if errors.As(err, &tooLarge) {
			http.Error(w, "too large", 413)
		} else {
			http.Error(w, "invalid JSON", 400)
		}
		return
	}
	if v.Count == nil || *v.Count < 0 || *v.Count > 10 {
		http.Error(w, "count must be 0..10", 422)
		return
	}
	w.WriteHeader(204)
}
func upload(w http.ResponseWriter, r *http.Request) {
	r.Body = http.MaxBytesReader(w, r.Body, 1024)
	defer r.Body.Close()
	err := r.ParseMultipartForm(64)
	if r.MultipartForm != nil {
		defer r.MultipartForm.RemoveAll()
	}
	if err != nil {
		http.Error(w, "bad multipart", 400)
		return
	}
	f, _, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "file required", 400)
		return
	}
	defer f.Close()
	n, err := io.Copy(io.Discard, f)
	if err != nil {
		http.Error(w, "read failed", 400)
		return
	}
	_, _ = fmt.Fprintf(w, "bytes=%d", n)
}
func events(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	ctl := http.NewResponseController(w)
	for i := 1; i <= 2; i++ {
		select {
		case <-r.Context().Done():
			return
		default:
		}
		if err := ctl.SetWriteDeadline(time.Now().Add(time.Second)); err != nil {
			return
		}
		if _, err := fmt.Fprintf(w, "id: %d\ndata: %d\n\n", i, i); err != nil {
			return
		}
		if err := ctl.Flush(); err != nil {
			return
		}
	}
}
func TestJSONBoundary(t *testing.T) {
	for _, tc := range []struct {
		body string
		want int
	}{
		{`{"count":0}`, 204}, {`{}`, 422}, {`{"count":null}`, 422},
		{`{"count":11}`, 422}, {`{"count":0,"extra":1}`, 400},
		{`{"count":0}{}`, 400}, {`{"count":0}` + strings.Repeat(" ", 128), 413},
	} {
		r := httptest.NewRequest("POST", "/", strings.NewReader(tc.body))
		r.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		jsonHandler(w, r)
		if w.Code != tc.want {
			t.Errorf("body=%q got=%d want=%d", tc.body, w.Code, tc.want)
		}
	}
	var value map[string]any
	d := json.NewDecoder(strings.NewReader(`{"id":9007199254740993}`))
	d.UseNumber()
	if err := d.Decode(&value); err != nil {
		t.Fatal(err)
	}
	if value["id"].(json.Number).String() != "9007199254740993" {
		t.Fatal(value)
	}
	zero := 0
	b, err := json.Marshal(struct {
		A int  `json:"a,omitempty"`
		B *int `json:"b,omitempty"`
	}{B: &zero})
	if err != nil || string(b) != `{"b":0}` {
		t.Fatalf("%s %v", b, err)
	}
	t.Log("size, missing/null, range, unknown fields, trailing JSON, omitempty, and UseNumber verified")
}
func TestUploadDownloadAndSSE(t *testing.T) {
	var body bytes.Buffer
	mw := multipart.NewWriter(&body)
	f, err := mw.CreateFormFile("file", "untrusted.txt")
	if err != nil {
		t.Fatal(err)
	}
	if _, err = io.WriteString(f, "hello"); err != nil {
		t.Fatal(err)
	}
	if err = mw.Close(); err != nil {
		t.Fatal(err)
	}
	r := httptest.NewRequest("POST", "/upload", &body)
	r.Header.Set("Content-Type", mw.FormDataContentType())
	w := httptest.NewRecorder()
	upload(w, r)
	if w.Body.String() != "bytes=5" {
		t.Fatal(w.Body.String())
	}
	r = httptest.NewRequest("GET", "/download", nil)
	r.Header.Set("Range", "bytes=1-3")
	w = httptest.NewRecorder()
	http.ServeContent(w, r, "fixed.txt", time.Time{}, strings.NewReader("hello"))
	if w.Code != 206 || w.Body.String() != "ell" {
		t.Fatalf("%d %s", w.Code, w.Body.String())
	}
	s := httptest.NewServer(http.HandlerFunc(events))
	defer s.Close()
	c := &http.Client{Timeout: 2 * time.Second}
	defer c.CloseIdleConnections()
	resp, err := c.Get(s.URL)
	if err != nil {
		t.Fatal(err)
	}
	b, err := io.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil || string(b) != "id: 1\ndata: 1\n\nid: 2\ndata: 2\n\n" {
		t.Fatalf("%q %v", b, err)
	}
	t.Log("multipart cleanup path, range response, and finite flushed SSE stream verified")
}
