package handler

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestRecognizeVirtualConvert_MatrixAndPrecedence(t *testing.T) {
	root := t.TempDir()
	write := func(rel string) {
		p := filepath.Join(root, filepath.FromSlash(rel))
		if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
			t.Fatal(err)
		}
		if err := os.WriteFile(p, []byte("x"), 0o644); err != nil {
			t.Fatal(err)
		}
	}

	// Sources on disk: doc.md, only.docx, both.md + both.docx, page.html.
	write("doc.md")
	write("only.docx")
	write("both.md")
	write("both.docx")
	write("page.html")

	cases := []struct {
		name       string
		url        string
		wantOK     bool
		wantSrcExt string
		wantFormat string
	}{
		{"md→docx", "/doc.docx", true, ".md", "docx"},
		{"md→html", "/doc.html", true, ".md", "html"},
		{"md→pdf", "/doc.pdf", true, ".md", "pdf"},
		{"docx→md (only docx present)", "/only.md", true, ".docx", "md"},
		{"docx→html (only docx present)", "/only.html", true, ".docx", "html"},
		{"docx has no pdf source", "/only.pdf", false, "", ""},
		{"both present, html prefers md source", "/both.html", true, ".md", "html"},
		{"html→md", "/page.md", true, ".html", "md"},
		{"html→docx", "/page.docx", true, ".html", "docx"},
		{"no source at all", "/missing.html", false, "", ""},
		{"directory url ignored", "/doc/", false, "", ""},
		{"non-convertible target", "/doc.txt", false, "", ""},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			src, format, ok := RecognizeVirtualConvert(root, c.url)
			if ok != c.wantOK {
				t.Fatalf("ok=%v want %v (src=%q format=%q)", ok, c.wantOK, src, format)
			}
			if !ok {
				return
			}
			if format != c.wantFormat {
				t.Errorf("format=%q want %q", format, c.wantFormat)
			}
			if filepath.Ext(src) != c.wantSrcExt {
				t.Errorf("source ext=%q want %q (src=%q)", filepath.Ext(src), c.wantSrcExt, src)
			}
		})
	}
}

func TestServeFrontMatterTemplate(t *testing.T) {
	rec := httptest.NewRecorder()
	ServeFrontMatterTemplate(rec, httptest.NewRequest(http.MethodGet, FrontMatterTemplatePath, nil))
	if rec.Code != http.StatusOK {
		t.Fatalf("status=%d, want 200", rec.Code)
	}
	if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "application/json") {
		t.Errorf("Content-Type=%q, want application/json", ct)
	}
	var payload struct {
		Placeholder string `json:"placeholder"`
		Fields      []struct {
			Name string `json:"name"`
			Hint string `json:"hint"`
		} `json:"fields"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
		t.Fatalf("decode: %v; body=%s", err, rec.Body.String())
	}
	if len(payload.Fields) == 0 {
		t.Fatal("fields empty")
	}
	// doctype/numbering have no other source; revision/status are template
	// fields an author can override here — all must be communicated.
	for _, want := range []string{"doctype", "numbering", "revision", "status", "tracking_number"} {
		if !strings.Contains(payload.Placeholder, want) {
			t.Errorf("placeholder missing %q: %q", want, payload.Placeholder)
		}
	}
	// HEAD returns headers, no body.
	hrec := httptest.NewRecorder()
	ServeFrontMatterTemplate(hrec, httptest.NewRequest(http.MethodHead, FrontMatterTemplatePath, nil))
	if hrec.Code != http.StatusOK || hrec.Body.Len() != 0 {
		t.Errorf("HEAD: status=%d bodylen=%d, want 200 + empty", hrec.Code, hrec.Body.Len())
	}
	// Non-GET/HEAD is rejected.
	prec := httptest.NewRecorder()
	ServeFrontMatterTemplate(prec, httptest.NewRequest(http.MethodPost, FrontMatterTemplatePath, nil))
	if prec.Code != http.StatusMethodNotAllowed {
		t.Errorf("POST: status=%d, want 405", prec.Code)
	}
}
