package finance

import (
	"context"
	"os"
	"strconv"
	"testing"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
)

func newReversalTestPool(t *testing.T) *pgxpool.Pool {
	url := os.Getenv("KM_ACCOUNTING_DISPOSABLE_TEST_DATABASE_URL")
	if url == "" {
		t.Skip("KM_ACCOUNTING_DISPOSABLE_TEST_DATABASE_URL is not set; reversal tests require a disposable isolated database")
	}
	pool, err := pgxpool.New(context.Background(), url)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(pool.Close)
	return pool
}

func TestJournalReversalGuardsOnIsolatedPostgres(t *testing.T) {
	pool := newReversalTestPool(t)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	tx, err := pool.Begin(ctx)
	if err != nil {
		t.Fatal(err)
	}
	defer tx.Rollback(context.Background())

	nonce := strconv.FormatInt(time.Now().UTC().UnixNano(), 36)
	var actorID, assetID, revenueID int64
	if err := tx.QueryRow(ctx, `INSERT INTO users(name,email,password,role) VALUES ('Reversal Test',$1,'unused','admin') RETURNING id`, "journal-reversal-"+nonce+"@example.invalid").Scan(&actorID); err != nil {
		t.Fatal(err)
	}
	if err := tx.QueryRow(ctx, `SELECT id FROM accounts WHERE code='1000.01.01'`).Scan(&assetID); err != nil {
		t.Fatal(err)
	}
	if err := tx.QueryRow(ctx, `SELECT id FROM accounts WHERE code='4000.01'`).Scan(&revenueID); err != nil {
		t.Fatal(err)
	}
	entryDate := time.Now().UTC().Truncate(24 * time.Hour)

	insertPosted := func(number string, date time.Time, reversalOf *int64, debitAccount, creditAccount int64) int64 {
		t.Helper()
		var id int64
		if err := tx.QueryRow(ctx, `INSERT INTO journal_entries
			(journal_number,journal_type,entry_date,status,total_debit,total_credit,created_by,posted_by,posted_at,reversal_of)
			VALUES ($1,'general',$2,'draft',10,10,$3,$3,now(),$4) RETURNING id`, number, date, actorID, reversalOf).Scan(&id); err != nil {
			t.Fatal(err)
		}
		if _, err := tx.Exec(ctx, `INSERT INTO journal_lines(journal_entry_id,account_id,debit,credit)
			VALUES ($1,$2,10,0),($1,$3,0,10)`, id, debitAccount, creditAccount); err != nil {
			t.Fatal(err)
		}
		if _, err := tx.Exec(ctx, `UPDATE journal_entries SET status='posted' WHERE id=$1`, id); err != nil {
			t.Fatal(err)
		}
		return id
	}

	journalNumber := "REV-TEST-" + nonce
	originalID := insertPosted(journalNumber, entryDate, nil, assetID, revenueID)
	mirrorOriginalID := originalID
	mirrorID := insertPosted("REV-"+journalNumber, entryDate, &mirrorOriginalID, revenueID, assetID)
	if _, err := tx.Exec(ctx, `UPDATE journal_entries SET status='reversed',reversal_entry_id=$2,reversed_at=now() WHERE id=$1`, originalID, mirrorID); err != nil {
		t.Fatalf("valid exact reversal: %v", err)
	}

	var originalStatus, mirrorStatus string
	if err := tx.QueryRow(ctx, `SELECT status FROM journal_entries WHERE id=$1`, originalID).Scan(&originalStatus); err != nil {
		t.Fatal(err)
	}
	if err := tx.QueryRow(ctx, `SELECT status FROM journal_entries WHERE id=$1`, mirrorID).Scan(&mirrorStatus); err != nil {
		t.Fatal(err)
	}
	if originalStatus != "reversed" || mirrorStatus != "posted" {
		t.Fatalf("valid reversal states original=%s mirror=%s", originalStatus, mirrorStatus)
	}

	assertRejected := func(name string, statement string, args ...any) {
		t.Helper()
		if _, err := tx.Exec(ctx, "SAVEPOINT reversal_guard"); err != nil {
			t.Fatal(err)
		}
		_, err := tx.Exec(ctx, statement, args...)
		if err == nil {
			_, _ = tx.Exec(ctx, "ROLLBACK TO SAVEPOINT reversal_guard")
			t.Fatalf("%s unexpectedly succeeded", name)
		}
		if _, rollbackErr := tx.Exec(ctx, "ROLLBACK TO SAVEPOINT reversal_guard"); rollbackErr != nil {
			t.Fatalf("rollback after %s: %v", name, rollbackErr)
		}
	}
	assertRejected("posted header edit", `UPDATE journal_entries SET total_debit=11 WHERE id=$1`, mirrorID)
	assertRejected("posted line edit", `UPDATE journal_lines SET debit=11 WHERE journal_entry_id=$1 AND debit=10`, mirrorID)
	assertRejected("posted line delete", `DELETE FROM journal_lines WHERE journal_entry_id=$1`, mirrorID)
	assertRejected("posted header delete", `DELETE FROM journal_entries WHERE id=$1`, mirrorID)

	// A unique partial index prevents multiple reversal entries for one original.
	assertRejected("duplicate reversal source", `INSERT INTO journal_entries
		(journal_number,journal_type,entry_date,status,total_debit,total_credit,reversal_of)
		VALUES ($1,'general',$2,'draft',10,10,$3)`, "REV-DUP-"+nonce, entryDate, originalID)

	// Invalid original ID is rejected by the source FK before posting.
	assertRejected("invalid reversal source", `INSERT INTO journal_entries
		(journal_number,journal_type,entry_date,status,total_debit,total_credit,reversal_of)
		VALUES ($1,'general',$2,'draft',10,10,9223372036854775807)`, "REV-MISSING-"+nonce, entryDate)

}
