package git

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strings"
)

const (
	attributionStart = "<!-- attribution:start -->"
	attributionEnd   = "<!-- attribution:end -->"
)

var attributionBlockRe = regexp.MustCompile(`(?s)` + regexp.QuoteMeta(attributionStart) + `.*?` + regexp.QuoteMeta(attributionEnd))

// StageAttribution rewrites index.html in the staged index only — the working
// tree is untouched, so the workspace (previews, builds) never carries the
// mark and a crashed push can't leave it behind. It injects the badge before
// </body> inside replaceable attribution markers (idempotent) and prepends a
// comment banner, then writes the result as a blob (git hash-object -w) and
// points the index at it (git update-index --cacheinfo). No-op when
// index.html isn't staged (e.g. the project has no root index.html).
func (r *Repo) StageAttribution(ctx context.Context, badgeHTML, comment string) error {
	staged, err := run(ctx, r.Dir, "show", ":index.html")
	if err != nil {
		return nil // index.html not staged — nothing to mark
	}

	marked := injectAttribution(staged, badgeHTML, comment)
	if marked == staged {
		return nil
	}

	sha, err := hashObject(ctx, r.Dir, marked)
	if err != nil {
		return fmt.Errorf("hash-object: %w", err)
	}
	if out, err := run(ctx, r.Dir, "update-index", "--cacheinfo", "100644,"+sha+",index.html"); err != nil {
		return fmt.Errorf("update-index: %s", out)
	}
	return nil
}

// injectAttribution adds the badge (replacing any existing attribution block)
// and the comment banner. Pure string transform, mirrored from Laravel's
// CopyrightMarkService so both egress paths produce the same shape.
func injectAttribution(html, badgeHTML, comment string) string {
	block := attributionStart + "\n" + badgeHTML + "\n" + attributionEnd

	if attributionBlockRe.MatchString(html) {
		html = attributionBlockRe.ReplaceAllString(html, block)
	} else if strings.Contains(html, "</body>") {
		html = strings.Replace(html, "</body>", "    "+block+"\n  </body>", 1)
	} else {
		html = html + "\n" + block + "\n"
	}

	// Defense in depth: Laravel already neutralizes comment terminators, but
	// guard here too so a "-->" in the comment can't close the HTML comment
	// early and inject live markup into the pushed source.
	banner := "<!-- " + strings.ReplaceAll(comment, "-->", "-- >") + " -->"
	if !strings.Contains(html, banner) {
		html = banner + "\n" + html
	}
	return html
}

// hashObject writes content into the object database and returns its sha.
func hashObject(ctx context.Context, dir, content string) (string, error) {
	cmd := exec.CommandContext(ctx, "git", "hash-object", "-w", "--stdin")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0", "GIT_ASKPASS=true")
	cmd.Stdin = strings.NewReader(content)
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &out
	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("%s", out.String())
	}
	return strings.TrimSpace(out.String()), nil
}
