package db

import (
	"database/sql"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

const mig0024 = "0024_normalise_callsign.sql"

// openWithUnappliedRewrite builds a logbook that already holds QSOs and has not
// yet had the callsign-normalising migration applied — i.e. exactly what an
// existing installation looks like the moment the auto-updater relaunches it.
func openWithUnappliedRewrite(t *testing.T, rows [][2]string) string {
	t.Helper()
	p := filepath.Join(t.TempDir(), "logbook.db")
	conn, err := Open(p)
	if err != nil {
		t.Fatal(err)
	}
	for _, r := range rows {
		if _, err := conn.Exec(
			`INSERT INTO qso (callsign, qso_date, band, mode) VALUES (?, ?, '20m', 'SSB')`, r[0], r[1]); err != nil {
			t.Fatal(err)
		}
	}
	// Rewind so the migration runs against real data on the next Open.
	if _, err := conn.Exec(`DELETE FROM schema_migrations WHERE name = ?`, mig0024); err != nil {
		t.Fatal(err)
	}
	if err := conn.Close(); err != nil {
		t.Fatal(err)
	}
	return p
}

func callsigns(t *testing.T, path string) []string {
	t.Helper()
	conn, err := sql.Open("sqlite", "file:"+path)
	if err != nil {
		t.Fatal(err)
	}
	defer conn.Close()
	rows, err := conn.Query(`SELECT callsign FROM qso ORDER BY id`)
	if err != nil {
		t.Fatal(err)
	}
	defer rows.Close()
	var out []string
	for rows.Next() {
		var c string
		if err := rows.Scan(&c); err != nil {
			t.Fatal(err)
		}
		out = append(out, c)
	}
	return out
}

// The auto-updater migrates before the operator ever sees the changelog, so the
// app has to take the safety copy itself. The copy must hold the data as it was
// BEFORE the rewrite — a copy of the already-migrated rows would be worthless.
func TestRewriteMigrationBacksUpOriginalData(t *testing.T) {
	p := openWithUnappliedRewrite(t, [][2]string{
		{"f5lit", "2026-07-01"},
		{" Pa3Eyf ", "2026-07-02"},
		{"F4BPO", "2026-07-03"},
	})

	var logged []string
	LogSink = func(f string, a ...any) { logged = append(logged, fmt.Sprintf(f, a...)) }
	defer func() { LogSink = nil }()

	conn, err := Open(p)
	if err != nil {
		t.Fatal(err)
	}
	conn.Close()

	// The live logbook is normalised.
	if got, want := callsigns(t, p), []string{"F5LIT", "PA3EYF", "F4BPO"}; strings.Join(got, ",") != strings.Join(want, ",") {
		t.Errorf("logbook callsigns = %v, want %v", got, want)
	}

	// The backup exists, next to the logbook, named after the migration.
	backup := p + ".pre-0024_normalise_callsign.bak"
	if _, err := os.Stat(backup); err != nil {
		t.Fatalf("no safety copy at %s: %v\nlogged:\n%s", backup, err, strings.Join(logged, "\n"))
	}
	// …and it holds the ORIGINAL rows, untouched.
	if got, want := callsigns(t, backup), []string{"f5lit", " Pa3Eyf ", "F4BPO"}; strings.Join(got, ",") != strings.Join(want, ",") {
		t.Errorf("backup callsigns = %v, want the pre-migration values %v", got, want)
	}

	var sawBackup bool
	for _, l := range logged {
		if strings.Contains(l, "backed up 3 QSO(s)") {
			sawBackup = true
		}
	}
	if !sawBackup {
		t.Errorf("the backup was not logged; got:\n%s", strings.Join(logged, "\n"))
	}
}

// No QSOs, nothing to protect: the settings database and a freshly created
// logbook must not litter the folder with pointless copies.
func TestRewriteMigrationSkipsBackupWhenNoQSOs(t *testing.T) {
	p := openWithUnappliedRewrite(t, nil)
	conn, err := Open(p)
	if err != nil {
		t.Fatal(err)
	}
	conn.Close()
	if _, err := os.Stat(p + ".pre-0024_normalise_callsign.bak"); err == nil {
		t.Error("an empty database should not be backed up")
	}
}

// schema_migrations is what makes "runs once" a guarantee rather than a promise:
// a second launch must neither re-run the rewrite nor take a second copy.
func TestRewriteMigrationRunsOnce(t *testing.T) {
	p := openWithUnappliedRewrite(t, [][2]string{{"f5lit", "2026-07-01"}})
	conn, err := Open(p)
	if err != nil {
		t.Fatal(err)
	}
	conn.Close()

	backup := p + ".pre-0024_normalise_callsign.bak"
	first, err := os.Stat(backup)
	if err != nil {
		t.Fatal(err)
	}

	var logged []string
	LogSink = func(f string, a ...any) { logged = append(logged, fmt.Sprintf(f, a...)) }
	defer func() { LogSink = nil }()

	conn2, err := Open(p) // second launch
	if err != nil {
		t.Fatal(err)
	}
	conn2.Close()

	for _, l := range logged {
		if strings.Contains(l, mig0024) {
			t.Errorf("migration ran again on the second launch: %s", l)
		}
	}
	second, err := os.Stat(backup)
	if err != nil {
		t.Fatal(err)
	}
	if !first.ModTime().Equal(second.ModTime()) {
		t.Error("the existing safety copy was overwritten on the second launch")
	}
}
