package web

import (
	"strings"
	"testing"

	"git.fabledsword.com/bvandeusen/minstrel/internal/config"
)

const sampleIndexHTML = `<!doctype html>
<html><head>
<title>{{ .AppName }}</title>
<meta property="og:title" content="{{ .AppName }}">
<meta property="og:description" content="{{ .Description }}">
<script>window.__MINSTREL__ = { appName: "{{ .AppName }}", description: "{{ .Description }}" };</script>
</head><body></body></html>`

func TestApplyBrandingTemplate_Defaults(t *testing.T) {
	out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
		AppName:     "Minstrel",
		Description: "Self-hosted music server.",
	})
	if err != nil {
		t.Fatalf("apply: %v", err)
	}
	body := string(out)
	if !strings.Contains(body, "<title>Minstrel</title>") {
		t.Errorf("missing default title; body=%s", body)
	}
}

func TestApplyBrandingTemplate_CustomBranding(t *testing.T) {
	out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
		AppName:     "Family Jukebox",
		Description: "Our home library.",
	})
	if err != nil {
		t.Fatalf("apply: %v", err)
	}
	body := string(out)
	if !strings.Contains(body, "<title>Family Jukebox</title>") {
		t.Errorf("custom AppName not in title; body=%s", body)
	}
	if !strings.Contains(body, `og:title" content="Family Jukebox"`) {
		t.Errorf("custom AppName not in og:title; body=%s", body)
	}
	if !strings.Contains(body, `appName: "Family Jukebox"`) {
		t.Errorf("custom AppName not in inline window.__MINSTREL__; body=%s", body)
	}
}

func TestApplyBrandingTemplate_XSSEscape_ScriptContext(t *testing.T) {
	// Operator config is trusted, but defensive escaping is still required:
	// an operator who doesn't realize their value is interpolated into HTML
	// shouldn't be able to accidentally break out of contexts.
	out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
		AppName:     "</script><img onerror=\"alert(1)\">",
		Description: "ok",
	})
	if err != nil {
		t.Fatalf("apply: %v", err)
	}
	body := string(out)

	// With html/template escaping intact, the inline-script block has
	// exactly one </script>. If escaping silently regressed (e.g. someone
	// swapped html/template for text/template), the AppName payload would
	// inject a second one and break out of the JS-string context.
	if got := strings.Count(body, "</script>"); got != 1 {
		t.Errorf("expected exactly one </script> in body (escaping intact), got %d; body=%q", got, body)
	}

	// And the JS object literal's closing `};` should still live inside
	// the script block — i.e. the block didn't get truncated by the payload.
	scriptOpen := strings.Index(body, "<script>window.__MINSTREL__")
	if scriptOpen < 0 {
		t.Fatalf("inline script block missing; body=%q", body)
	}
	scriptClose := strings.Index(body[scriptOpen:], "</script>")
	if scriptClose < 0 {
		t.Fatalf("inline script block has no close; body=%q", body)
	}
	scriptBody := body[scriptOpen : scriptOpen+scriptClose]
	if !strings.Contains(scriptBody, "};") {
		t.Errorf("inline script block truncated before object literal closed: %q", scriptBody)
	}
}

func TestApplyBrandingTemplate_XSSEscape_AttrContext(t *testing.T) {
	out, err := applyBrandingTemplate(sampleIndexHTML, config.BrandingConfig{
		AppName:     `"><img onerror="alert(1)">`,
		Description: "ok",
	})
	if err != nil {
		t.Fatalf("apply: %v", err)
	}
	body := string(out)
	// The unescaped HTML should NOT appear as a live tag in attribute context.
	if strings.Contains(body, `"><img onerror="alert(1)">`) {
		t.Errorf("unescaped attribute breakout: %s", body)
	}
}
