package restdemo

import (
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
)

type item struct {
	Title string `json:"title"`
}
type store struct {
	mu    sync.Mutex
	value item
}

func (s *store) etag() string { return fmt.Sprintf(`"%x"`, sha256.Sum256([]byte(s.value.Title))) }
func problem(w http.ResponseWriter, status int) {
	w.Header().Set("Content-Type", "application/problem+json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(map[string]any{"type": "about:blank", "title": http.StatusText(status), "status": status})
}
func (s *store) handler() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /items/1", func(w http.ResponseWriter, r *http.Request) {
		s.mu.Lock()
		defer s.mu.Unlock()
		tag := s.etag()
		w.Header().Set("ETag", tag)
		w.Header().Set("Cache-Control", "private, no-cache")
		// This exercise accepts exactly one strong ETag, not the full HTTP list grammar.
		if r.Header.Get("If-None-Match") == tag {
			w.WriteHeader(304)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		if r.Method == http.MethodHead {
			return
		}
		_ = json.NewEncoder(w).Encode(s.value)
	})
	mux.HandleFunc("PUT /items/1", func(w http.ResponseWriter, r *http.Request) {
		r.Body = http.MaxBytesReader(w, r.Body, 256)
		defer r.Body.Close()
		var next item
		d := json.NewDecoder(r.Body)
		d.DisallowUnknownFields()
		if err := d.Decode(&next); err != nil || next.Title == "" {
			problem(w, 400)
			return
		}
		var extra any
		if err := d.Decode(&extra); err != io.EOF {
			problem(w, 400)
			return
		}
		s.mu.Lock()
		defer s.mu.Unlock()
		tag := r.Header.Get("If-Match")
		if tag == "" {
			problem(w, 428)
			return
		}
		if tag != s.etag() {
			problem(w, 412)
			return
		}
		s.value = next
		// No ETag here: this server transforms JSON on storage, so PUT validator rules apply.
		w.WriteHeader(204)
	})
	return mux
}
func TestConditionalUpdate(t *testing.T) {
	s := &store{value: item{Title: "before"}}
	h := s.handler()
	call := func(method, body, header, value string) *httptest.ResponseRecorder {
		r := httptest.NewRequest(method, "/items/1", strings.NewReader(body))
		if header != "" {
			r.Header.Set(header, value)
		}
		w := httptest.NewRecorder()
		h.ServeHTTP(w, r)
		return w
	}
	first := call("GET", "", "", "")
	old := first.Header().Get("ETag")
	if old == "" {
		t.Fatal("missing validator")
	}
	if w := call("GET", "", "If-None-Match", old); w.Code != 304 {
		t.Fatal(w.Code)
	}
	if w := call("PUT", `{"title":"after"}`, "If-Match", old); w.Code != 204 {
		t.Fatal(w.Code)
	}
	if w := call("PUT", `{"title":"lost update"}`, "If-Match", old); w.Code != 412 || w.Header().Get("Content-Type") != "application/problem+json" {
		t.Fatal(w.Code)
	}
	current := call("GET", "", "", "").Header().Get("ETag")
	for i := 0; i < 2; i++ {
		if w := call("PUT", `{"title":"after"}`, "If-Match", current); w.Code != 204 {
			t.Fatal(w.Code)
		}
	}
	if s.value.Title != "after" {
		t.Fatal("unexpected final state")
	}
	t.Log("304, stale-write 412, and repeated replacement final state verified")
}
