package requestdemo

import (
	"bufio"
	"context"
	"encoding/json"
	jsonv2 "encoding/json/v2"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

func TestJSONTeaching(t *testing.T) {
	zero := 0
	v := struct {
		Count   int       `json:"count,omitempty"`
		Present *int      `json:"present,omitempty"`
		Tags    []string  `json:"tags"`
		When    time.Time `json:"when,omitempty"`
		Omit    time.Time `json:"omit,omitzero"`
	}{Present: &zero}
	b, err := json.Marshal(v)
	if err != nil {
		t.Fatal(err)
	}
	t.Log("v1:", string(b))
	b, err = jsonv2.Marshal(v)
	if err != nil {
		t.Fatal(err)
	}
	t.Log("v2:", string(b))
	for _, in := range []string{`{}`, `{"count":null}`, `{"count":0}`} {
		var x input
		if err := json.Unmarshal([]byte(in), &x); err != nil {
			t.Fatal(err)
		}
		t.Logf("%s present=%v", in, x.Count != nil)
	}
	var generic map[string]any
	if err := json.Unmarshal([]byte(`{"id":9007199254740993}`), &generic); err != nil {
		t.Fatal(err)
	}
	t.Logf("any: %T %.0f", generic["id"], generic["id"])
	var typed struct {
		ID int64 `json:"id"`
	}
	if err := json.Unmarshal([]byte(`{"id":9007199254740993}`), &typed); err != nil {
		t.Fatal(err)
	}
	t.Log("int64:", typed.ID)
	var duplicate struct {
		N int `json:"n"`
	}
	if err := json.Unmarshal([]byte(`{"n":1,"n":2}`), &duplicate); err != nil {
		t.Fatal(err)
	}
	t.Log("v1 duplicate:", duplicate.N)
	err = jsonv2.Unmarshal([]byte(`{"n":1,"n":2}`), &duplicate)
	t.Log("v2 duplicate rejected:", err != nil)
	if err == nil {
		t.Fatal("duplicate must be rejected")
	}
}

func TestIncrementalSSEAndCancel(t *testing.T) {
	release := make(chan struct{})
	stopped := make(chan struct{})
	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer close(stopped)
		w.Header().Set("Content-Type", "text/event-stream")
		ctl := http.NewResponseController(w)
		if err := ctl.SetWriteDeadline(time.Now().Add(2 * time.Second)); err != nil {
			return
		}
		if _, err := fmt.Fprint(w, "id: 1\ndata: ready\n\n"); err != nil {
			return
		}
		if err := ctl.Flush(); err != nil {
			return
		}
		select {
		case <-release:
		case <-r.Context().Done():
			return
		}
		<-r.Context().Done()
	}))
	defer s.Close()
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	req, err := http.NewRequestWithContext(ctx, "GET", s.URL, nil)
	if err != nil {
		t.Fatal(err)
	}
	c := &http.Client{}
	defer c.CloseIdleConnections()
	resp, err := c.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()
	scanner := bufio.NewScanner(resp.Body)
	var lines []string
	for scanner.Scan() {
		if scanner.Text() == "" {
			break
		}
		lines = append(lines, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		t.Fatal(err)
	}
	if strings.Join(lines, "|") != "id: 1|data: ready" {
		t.Fatal(lines)
	}
	select {
	case <-stopped:
		t.Fatal("handler ended before release")
	default:
	}
	t.Log("first event arrived while handler remained active")
	close(release)
	cancel()
	select {
	case <-stopped:
		t.Log("handler observed client cancellation")
	case <-time.After(2 * time.Second):
		t.Fatal("cancel not observed")
	}
}

func TestBoundaryStatusOutput(t *testing.T) {
	for _, body := range []string{`{"count":0}`, `{}`, `{"count":null}`, `{"count":11}`, `{"count":0,"extra":1}`, `{"count":0}{}`} {
		w := httptest.NewRecorder()
		r := httptest.NewRequest("POST", "/", strings.NewReader(body))
		r.Header.Set("Content-Type", "application/json")
		jsonHandler(w, r)
		t.Logf("%s -> %d", body, w.Code)
	}
	r := httptest.NewRequest("POST", "/", strings.NewReader(`{"count":0}`))
	r.Header.Set("Content-Type", "text/plain")
	w := httptest.NewRecorder()
	jsonHandler(w, r)
	if w.Code != 415 {
		t.Fatal(w.Code)
	}
	r = httptest.NewRequest("POST", "/", strings.NewReader(strings.Repeat("x", 2048)))
	r.Header.Set("Content-Type", "multipart/form-data; boundary=demo")
	w = httptest.NewRecorder()
	upload(w, r)
	if w.Code != 400 {
		t.Fatal(w.Code)
	}
	t.Log("wrong media type=415; malformed/oversized upload rejected")
}

var jsonInput = []byte(`{"id":42,"name":"gopher","tags":["go","http"]}`)

type payload struct {
	ID   int      `json:"id"`
	Name string   `json:"name"`
	Tags []string `json:"tags"`
}

var sink payload

func BenchmarkJSON(b *testing.B) {
	b.Run("v1-typed", func(b *testing.B) {
		for b.Loop() {
			var x payload
			if err := json.Unmarshal(jsonInput, &x); err != nil {
				b.Fatal(err)
			}
			sink = x
		}
	})
	b.Run("v2-typed", func(b *testing.B) {
		for b.Loop() {
			var x payload
			if err := jsonv2.Unmarshal(jsonInput, &x); err != nil {
				b.Fatal(err)
			}
			sink = x
		}
	})
}

func Example_serveContent() {
	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"))
	res := w.Result()
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(res.StatusCode, string(body), res.Header.Get("Content-Range"))
	// Output:
	// 206 ell bytes 1-3/5
}
