package handlers

import (
	"archive/zip"
	"bytes"
	"io"
	"strings"
	"testing"
)

func buildXLSX(t *testing.T, entries map[string]string) []byte {
	t.Helper()
	var buf bytes.Buffer
	zw := zip.NewWriter(&buf)
	for name, content := range entries {
		w, err := zw.Create(name)
		if err != nil {
			t.Fatalf("zip create %s: %v", name, err)
		}
		if _, err := io.WriteString(w, content); err != nil {
			t.Fatalf("zip write %s: %v", name, err)
		}
	}
	if err := zw.Close(); err != nil {
		t.Fatalf("zip close: %v", err)
	}
	return buf.Bytes()
}

func readZipEntry(t *testing.T, payload []byte, name string) string {
	t.Helper()
	zr, err := zip.NewReader(bytes.NewReader(payload), int64(len(payload)))
	if err != nil {
		t.Fatalf("output is not a valid zip: %v", err)
	}
	for _, f := range zr.File {
		if f.Name != name {
			continue
		}
		rc, err := f.Open()
		if err != nil {
			t.Fatalf("open entry %s: %v", name, err)
		}
		defer rc.Close()
		b, err := io.ReadAll(rc)
		if err != nil {
			t.Fatalf("read entry %s: %v", name, err)
		}
		return string(b)
	}
	t.Fatalf("entry %s not found in output zip", name)
	return ""
}

const sampleWorkbookXML = `<?xml version="1.0"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheets><sheet name="Sheet1" sheetId="1"/></sheets></workbook>`

// A template with no VML drawings must pass through byte-for-byte and stay a
// valid ZIP — the sanitizer must never touch non-target entries.
func TestSanitizeXLSXPayload_NoVML_Unchanged(t *testing.T) {
	src := buildXLSX(t, map[string]string{
		"[Content_Types].xml": `<?xml version="1.0"?><Types/>`,
		"xl/workbook.xml":     sampleWorkbookXML,
	})

	out, fixes, changed, err := sanitizeXLSXPayload(src)
	if err != nil {
		t.Fatalf("sanitize error: %v", err)
	}
	if changed {
		t.Fatalf("expected changed=false for a payload without VML drawings")
	}
	if fixes != 0 {
		t.Fatalf("expected 0 style fixes, got %d", fixes)
	}
	if !bytes.Equal(out, src) {
		t.Fatalf("payload without VML must be returned verbatim")
	}
	if _, err := zip.NewReader(bytes.NewReader(out), int64(len(out))); err != nil {
		t.Fatalf("output not a valid zip: %v", err)
	}
}

// A malformed VML style must be rewritten, and the result must remain a valid
// ZIP whose OTHER entries are preserved intact.
func TestSanitizeXLSXPayload_MalformedVML_RewrittenAndValid(t *testing.T) {
	malformedVML := `<xml xmlns:v="urn:schemas-microsoft-com:vml"><v:shape style='position:absolute;12pt;24pt;width:100pt'/></xml>`
	src := buildXLSX(t, map[string]string{
		"[Content_Types].xml":         `<?xml version="1.0"?><Types/>`,
		"xl/workbook.xml":             sampleWorkbookXML,
		"xl/drawings/vmlDrawing1.vml": malformedVML,
	})

	out, fixes, changed, err := sanitizeXLSXPayload(src)
	if err != nil {
		t.Fatalf("sanitize error: %v", err)
	}
	if !changed {
		t.Fatalf("expected changed=true for malformed VML style")
	}
	if fixes == 0 {
		t.Fatalf("expected style fixes > 0")
	}

	// Output must be a valid ZIP.
	if _, err := zip.NewReader(bytes.NewReader(out), int64(len(out))); err != nil {
		t.Fatalf("output not a valid zip: %v", err)
	}
	// The VML entry must now carry the normalized margin properties.
	vml := readZipEntry(t, out, "xl/drawings/vmlDrawing1.vml")
	if !bytes.Contains([]byte(vml), []byte("margin-left:12pt")) ||
		!bytes.Contains([]byte(vml), []byte("margin-top:24pt")) {
		t.Fatalf("VML not normalized as expected: %s", vml)
	}
	// A non-target entry must be preserved byte-for-byte.
	if got := readZipEntry(t, out, "xl/workbook.xml"); got != sampleWorkbookXML {
		t.Fatalf("non-VML entry mutated:\n got: %s\nwant: %s", got, sampleWorkbookXML)
	}
}

func TestSanitizeXLSXPayload_MalformedVML_WithPrefix_Rewritten(t *testing.T) {
	malformedVML := `<xml xmlns:v="urn:schemas-microsoft-com:vml"><v:shape style='mso-position-horizontal-relative:page;position:absolute;12pt;24pt;width:100pt'/></xml>`
	src := buildXLSX(t, map[string]string{
		"[Content_Types].xml":         `<?xml version="1.0"?><Types/>`,
		"xl/workbook.xml":             sampleWorkbookXML,
		"xl/drawings/vmlDrawing1.vml": malformedVML,
	})

	out, _, changed, err := sanitizeXLSXPayload(src)
	if err != nil {
		t.Fatalf("sanitize error: %v", err)
	}
	if !changed {
		t.Fatalf("expected changed=true for prefixed malformed VML style")
	}

	vml := readZipEntry(t, out, "xl/drawings/vmlDrawing1.vml")
	if !strings.Contains(vml, "mso-position-horizontal-relative:page;position:absolute;margin-left:12pt;margin-top:24pt") {
		t.Fatalf("prefixed VML not normalized as expected: %s", vml)
	}
}

func TestSanitizeXLSXPayload_DuplicateVMLShapeIDs_AreUniqued(t *testing.T) {
	vml := `<xml xmlns:v="urn:schemas-microsoft-com:vml">
  <v:shape id="_x0000_s1025" type="#_x0000_t202" style="position:absolute;12pt;24pt;width:100pt"/>
  <v:shape id="_x0000_s1025" type="#_x0000_t202" style="position:absolute;12pt;24pt;width:100pt"/>
  <v:shape id="_x0000_s1025" type="#_x0000_t202" style="position:absolute;12pt;24pt;width:100pt"/>
</xml>`
	src := buildXLSX(t, map[string]string{
		"[Content_Types].xml":         `<?xml version="1.0"?><Types/>`,
		"xl/workbook.xml":             sampleWorkbookXML,
		"xl/drawings/vmlDrawing1.vml": vml,
	})

	out, _, changed, err := sanitizeXLSXPayload(src)
	if err != nil {
		t.Fatalf("sanitize error: %v", err)
	}
	if !changed {
		t.Fatalf("expected changed=true for duplicate VML shape IDs")
	}

	outVML := readZipEntry(t, out, "xl/drawings/vmlDrawing1.vml")
	if strings.Count(outVML, `id="_x0000_s1025"`) != 1 {
		t.Fatalf("expected original shape id to appear once, got: %s", outVML)
	}
	if strings.Count(outVML, `id="_x0000_s1026"`) != 1 || strings.Count(outVML, `id="_x0000_s1027"`) != 1 {
		t.Fatalf("expected duplicate ids to be uniqued, got: %s", outVML)
	}
}
