package services

import (
	"bytes"
	"context"
	"os"
	"strings"
	"testing"

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"
	_ "github.com/lib/pq"
)

// TestResolveOrgSheets_LiveSchemaSnapshot probes the live paliad schema
// the way the backup runner does at the start of every run, then asserts
// that every spec the registry declares either keeps all its ORDER BY
// columns or — if any are missing — composes a fallback SELECT that the
// DB can still execute. Catches the m/paliad#140 class of bug
// (hardcoded ORDER BY against a renamed column) before deploy.
//
// Skipped when TEST_DATABASE_URL is unset. Read-only: opens a
// REPEATABLE READ tx, never writes.
func TestResolveOrgSheets_LiveSchemaSnapshot(t *testing.T) {
	url := os.Getenv("TEST_DATABASE_URL")
	if url == "" {
		t.Skip("TEST_DATABASE_URL not set — skipping live DB test")
	}
	pool, err := sqlx.Connect("postgres", url)
	if err != nil {
		t.Fatalf("connect: %v", err)
	}
	defer pool.Close()

	ctx := context.Background()
	specs := orgSheetSpecs()
	sheets, err := resolveOrgSheets(ctx, pool, specs)
	if err != nil {
		t.Fatalf("resolveOrgSheets: %v", err)
	}
	if len(sheets) != len(specs) {
		t.Fatalf("resolved %d sheets, want %d", len(sheets), len(specs))
	}

	// Each resolved SELECT must run cleanly against the live schema.
	// We LIMIT 1 inside a sub-SELECT so we don't materialise the full
	// table (some are large) but still exercise the ORDER BY clause.
	for _, sq := range sheets {
		wrapped := `SELECT * FROM (` + sq.SQL + `) _wrap LIMIT 1`
		if _, err := pool.QueryxContext(ctx, wrapped, sq.Args...); err != nil {
			t.Errorf("sheet %q SQL failed: %v\nSQL: %s", sq.SheetName, err, sq.SQL)
		}
	}
}

// TestWriteOrg_LiveSmoke runs the full ExportService.WriteOrg pipeline
// against a real DB: schema probe, REPEATABLE READ tx, every sheet
// query, xlsx + json + per-sheet CSV assembly, outer zip framing.
// Discards the bytes — this is a "does it crash" smoke, the bug class
// it catches is exactly the one from m/paliad#140 (hardcoded ORDER BY
// against a missing column).
//
// Skipped when TEST_DATABASE_URL is unset.
func TestWriteOrg_LiveSmoke(t *testing.T) {
	url := os.Getenv("TEST_DATABASE_URL")
	if url == "" {
		t.Skip("TEST_DATABASE_URL not set — skipping live DB test")
	}
	pool, err := sqlx.Connect("postgres", url)
	if err != nil {
		t.Fatalf("connect: %v", err)
	}
	defer pool.Close()

	svc := NewExportService(pool, "test-firm")
	var buf bytes.Buffer
	meta, err := svc.WriteOrg(context.Background(), &buf, ExportSpec{
		ActorID:    uuid.New(),
		ActorEmail: "backup-smoke@test.local",
		ActorLabel: "Backup Smoke",
	})
	if err != nil {
		t.Fatalf("WriteOrg: %v", err)
	}
	if buf.Len() == 0 {
		t.Fatalf("WriteOrg wrote no bytes")
	}
	// Spot-check meta fills.
	if meta.Scope != ExportScopeOrg {
		t.Errorf("meta.Scope = %q, want %q", meta.Scope, ExportScopeOrg)
	}
	if len(meta.RowCounts) != len(orgSheetSpecs()) {
		t.Errorf("meta.RowCounts has %d entries, want %d (one per sheet)", len(meta.RowCounts), len(orgSheetSpecs()))
	}
	// The bytes are a zip; the first 4 bytes are PK\x03\x04 for a non-empty zip.
	if buf.Len() >= 4 && !strings.HasPrefix(buf.String()[:4], "PK\x03\x04") {
		t.Errorf("bundle bytes don't look like a zip (first bytes: %x)", buf.Bytes()[:4])
	}
}
