package storage

import (
	"bytes"
	"testing"
)

// TestForcedDivergence_ConflictDetectedAndResolved is the gate C-14
// forced-divergence integration test. It simulates two peers writing to
// the same file *without* the flock (the lock was bypassed), detects the
// conflict via DetectConflicts, resolves it via ResolveConflict to the
// source peer's content, and verifies the resolution is deterministic
// across repeated runs.
//
// The test uses in-memory file maps (no real Syncthing is involved — it
// tests the CLI-side conflict detection + resolution logic, which is
// what orca runs on the lead node over the SSH-gathered peer views).
func TestForcedDivergence_ConflictDetectedAndResolved(t *testing.T) {
	// Two peers, same file, different content — no lock held.
	peerA := []byte("source-writes-this")
	peerB := []byte("peer-b-writes-that")
	peerFiles := map[string]map[string][]byte{
		"peer-a": {"data/db.sqlite": peerA},
		"peer-b": {"data/db.sqlite": peerB},
	}
	const sourcePeer = "peer-a"
	const ns = "divergence-ns"

	// Detect.
	conflicts, err := DetectConflicts(ns, peerFiles)
	if err != nil {
		t.Fatalf("DetectConflicts: %v", err)
	}
	if len(conflicts) != 1 {
		t.Fatalf("expected 1 conflict, got %d", len(conflicts))
	}
	c := conflicts[0]
	if c.Path != "data/db.sqlite" {
		t.Errorf("conflict path = %q, want %q", c.Path, "data/db.sqlite")
	}
	if len(c.Versions) != 2 {
		t.Errorf("conflict has %d versions, want 2", len(c.Versions))
	}

	// Resolve — source peer (the one holding the lock) wins.
	winner, losers := ResolveConflict(c, sourcePeer)
	if !bytes.Equal(winner, peerA) {
		t.Errorf("winning content = %q, want %q (source peer)", winner, peerA)
	}
	if len(losers) != 1 || losers[0] != "peer-b" {
		t.Errorf("losing peers = %v, want [peer-b]", losers)
	}

	// Deterministic: re-run detection + resolution, expect identical output.
	conflicts2, _ := DetectConflicts(ns, peerFiles)
	winner2, losers2 := ResolveConflict(conflicts2[0], sourcePeer)
	if !bytes.Equal(winner2, winner) {
		t.Errorf("non-deterministic winner: %q vs %q", winner2, winner)
	}
	if !equalStringSlices(losers2, losers) {
		t.Errorf("non-deterministic losers: %v vs %v", losers2, losers)
	}

	// Three-way divergence: source still wins deterministically.
	peerFiles3 := map[string]map[string][]byte{
		"peer-a": {"data/db.sqlite": peerA},
		"peer-b": {"data/db.sqlite": peerB},
		"peer-c": {"data/db.sqlite": []byte("peer-c-writes-something-else")},
	}
	conflicts3, _ := DetectConflicts(ns, peerFiles3)
	if len(conflicts3) != 1 {
		t.Fatalf("3-way: expected 1 conflict, got %d", len(conflicts3))
	}
	winner3, losers3 := ResolveConflict(conflicts3[0], sourcePeer)
	if !bytes.Equal(winner3, peerA) {
		t.Errorf("3-way winner = %q, want %q", winner3, peerA)
	}
	if len(losers3) != 2 {
		t.Errorf("3-way losers = %v, want 2 entries", losers3)
	}
	// Losers must be sorted for deterministic ordering.
	if !isSorted(losers3) {
		t.Errorf("losers not sorted: %v", losers3)
	}
}

// TestForcedDivergence_UnknownSource_Unresolved verifies the
// deterministic failure mode: when the source peer is unknown (the lock
// holder is not in the versions map), ResolveConflict returns (nil, nil)
// so the CLI can flag the conflict for manual resolution. No silent
// winner is picked.
func TestForcedDivergence_UnknownSource_Unresolved(t *testing.T) {
	peerFiles := map[string]map[string][]byte{
		"peer-a": {"f": []byte("a")},
		"peer-b": {"f": []byte("b")},
	}
	conflicts, _ := DetectConflicts("ns", peerFiles)
	if len(conflicts) != 1 {
		t.Fatalf("expected 1 conflict, got %d", len(conflicts))
	}
	// Source peer is "peer-z" — not in the versions map.
	winner, losers := ResolveConflict(conflicts[0], "peer-z")
	if winner != nil {
		t.Errorf("unknown source: winner = %v, want nil", winner)
	}
	if losers != nil {
		t.Errorf("unknown source: losers = %v, want nil", losers)
	}
}

// TestForcedDivergence_NoLockNoConflict simulates the normal path: two
// peers hold the same content for the same file (one wrote under the
// lock, the other synced read-only). DetectConflicts reports no
// conflict.
func TestForcedDivergence_NoLockNoConflict(t *testing.T) {
	content := []byte("same-content")
	peerFiles := map[string]map[string][]byte{
		"peer-a": {"data/f": content},
		"peer-b": {"data/f": content},
	}
	conflicts, err := DetectConflicts("ns", peerFiles)
	if err != nil {
		t.Fatalf("DetectConflicts: %v", err)
	}
	if len(conflicts) != 0 {
		t.Errorf("expected 0 conflicts, got %d: %+v", len(conflicts), conflicts)
	}
}

// equalStringSlices reports whether two string slices are equal in order.
func equalStringSlices(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

// isSorted reports whether the string slice is in ascending order.
func isSorted(s []string) bool {
	for i := 1; i < len(s); i++ {
		if s[i-1] > s[i] {
			return false
		}
	}
	return true
}
