package mirror

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"
)

func threeDirTargets(t *testing.T) []Target {
	t.Helper()
	ts := make([]Target, 3)
	for i := range ts {
		dir := t.TempDir()
		ts[i] = Target{Name: "m" + string(rune('1'+i)), Dir: dir, FetchURL: "file://" + dir}
	}
	return ts
}

func TestPublishAndConsistency(t *testing.T) {
	targets := threeDirTargets(t)
	content := []byte(`{"version":1,"sig":"x"}`)
	results, err := Publish(content, "endpoints.v1.json", targets)
	if err != nil {
		t.Fatalf("Publish: %v", err)
	}
	if len(results) != 3 {
		t.Fatalf("want 3 results, got %d", len(results))
	}
	want := SHA256Hex(content)
	if err := VerifyConsistency("endpoints.v1.json", targets, want); err != nil {
		t.Fatalf("VerifyConsistency: %v", err)
	}
}

func TestConsistencyDetectsCorruptMirror(t *testing.T) {
	targets := threeDirTargets(t)
	content := []byte(`{"version":1}`)
	if _, err := Publish(content, "doc.json", targets); err != nil {
		t.Fatal(err)
	}
	// Corrupt mirror #2.
	if err := os.WriteFile(filepath.Join(targets[1].Dir, "doc.json"), []byte("tampered"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := VerifyConsistency("doc.json", targets, SHA256Hex(content)); err == nil {
		t.Fatal("want consistency error for corrupt mirror")
	}
}

func TestFetchFailoverSkipsInvalid(t *testing.T) {
	targets := threeDirTargets(t)
	good := []byte("GOOD")
	// m1 has bad content, m2 good, m3 good.
	if err := os.WriteFile(filepath.Join(targets[0].Dir, "o.json"), []byte("BAD"), 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(targets[1].Dir, "o.json"), good, 0o644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(targets[2].Dir, "o.json"), good, 0o644); err != nil {
		t.Fatal(err)
	}
	validate := func(b []byte) error {
		if string(b) != "GOOD" {
			return errors.New("invalid")
		}
		return nil
	}
	got, winner, err := Fetch("o.json", targets, validate)
	if err != nil {
		t.Fatalf("Fetch: %v", err)
	}
	if string(got) != "GOOD" {
		t.Fatalf("got %q", got)
	}
	if winner != targets[1].Name {
		t.Fatalf("winner = %q, want %q", winner, targets[1].Name)
	}
}

func TestFetchFailoverHTTPMirrorDown(t *testing.T) {
	// First mirror: HTTP 500. Second mirror: serves the document.
	down := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer down.Close()
	up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("PAYLOAD"))
	}))
	defer up.Close()

	targets := []Target{
		{Name: "down", FetchURL: down.URL},
		{Name: "up", FetchURL: up.URL},
	}
	got, winner, err := Fetch("any.json", targets, nil)
	if err != nil {
		t.Fatalf("Fetch: %v", err)
	}
	if string(got) != "PAYLOAD" || winner != "up" {
		t.Fatalf("got=%q winner=%q", got, winner)
	}
}

func TestFetchAllFail(t *testing.T) {
	targets := []Target{{Name: "x", Dir: t.TempDir()}}
	if _, _, err := Fetch("missing.json", targets, nil); err == nil {
		t.Fatal("want error when all mirrors fail")
	}
}
