//go:build integration && !lite

package service_test

import (
	"context"
	"fmt"
	"testing"

	"breadbox/internal/pgconv"
	"breadbox/internal/service"
	"breadbox/internal/shortid"
	"breadbox/internal/testutil"

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

// --- Short ID generation (Postgres trigger) ---

func TestShortID_AutoGenerated_User(t *testing.T) {
	_, queries, _ := newService(t)
	user := testutil.MustCreateUser(t, queries, "ShortIDUser")

	if user.ShortID == "" {
		t.Fatal("expected user.ShortID to be populated by trigger")
	}
	if !shortid.IsShortID(user.ShortID) {
		t.Fatalf("user.ShortID %q is not a valid short ID", user.ShortID)
	}
}

func TestShortID_AutoGenerated_Account(t *testing.T) {
	_, queries, _ := newService(t)
	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_conn_1")
	acct := testutil.MustCreateAccount(t, queries, conn.ID, "sid_acct_1", "Checking")

	if !shortid.IsShortID(acct.ShortID) {
		t.Fatalf("account.ShortID %q is not a valid short ID", acct.ShortID)
	}
}

func TestShortID_AutoGenerated_Transaction(t *testing.T) {
	_, queries, _ := newService(t)
	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_conn_2")
	acct := testutil.MustCreateAccount(t, queries, conn.ID, "sid_acct_2", "Checking")
	txn := testutil.MustCreateTransaction(t, queries, acct.ID, "sid_txn_1", "Coffee", 450, "2025-03-15")

	if !shortid.IsShortID(txn.ShortID) {
		t.Fatalf("transaction.ShortID %q is not a valid short ID", txn.ShortID)
	}
}

func TestShortID_AutoGenerated_Connection(t *testing.T) {
	_, queries, _ := newService(t)
	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_conn_3")

	if !shortid.IsShortID(conn.ShortID) {
		t.Fatalf("connection.ShortID %q is not a valid short ID", conn.ShortID)
	}
}

func TestShortID_Unique_AcrossRows(t *testing.T) {
	_, queries, _ := newService(t)
	seen := make(map[string]bool)

	for i := 0; i < 20; i++ {
		u := testutil.MustCreateUser(t, queries, fmt.Sprintf("User_%d", i))
		if seen[u.ShortID] {
			t.Fatalf("duplicate short_id %q at iteration %d", u.ShortID, i)
		}
		seen[u.ShortID] = true
	}
}

// --- Short IDs in service responses ---

func TestShortID_InUserResponse(t *testing.T) {
	svc, queries, _ := newService(t)
	ctx := context.Background()

	dbUser := testutil.MustCreateUser(t, queries, "Alice")

	users, err := svc.ListUsers(ctx)
	if err != nil {
		t.Fatalf("ListUsers: %v", err)
	}
	if len(users) != 1 {
		t.Fatalf("expected 1 user, got %d", len(users))
	}
	if users[0].ShortID == "" {
		t.Fatal("expected ShortID in UserResponse")
	}
	if users[0].ShortID != dbUser.ShortID {
		t.Fatalf("ShortID mismatch: response=%q, db=%q", users[0].ShortID, dbUser.ShortID)
	}
}

func TestShortID_InAccountResponse(t *testing.T) {
	svc, queries, _ := newService(t)
	ctx := context.Background()

	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_resp_conn")
	dbAcct := testutil.MustCreateAccount(t, queries, conn.ID, "sid_resp_acct", "Checking")

	accounts, err := svc.ListAccounts(ctx, nil)
	if err != nil {
		t.Fatalf("ListAccounts: %v", err)
	}
	if len(accounts) != 1 {
		t.Fatalf("expected 1 account, got %d", len(accounts))
	}
	if accounts[0].ShortID != dbAcct.ShortID {
		t.Fatalf("ShortID mismatch: response=%q, db=%q", accounts[0].ShortID, dbAcct.ShortID)
	}
}

func TestShortID_InTransactionResponse(t *testing.T) {
	svc, queries, _ := newService(t)
	ctx := context.Background()

	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_txn_conn")
	acct := testutil.MustCreateAccount(t, queries, conn.ID, "sid_txn_acct", "Checking")
	dbTxn := testutil.MustCreateTransaction(t, queries, acct.ID, "sid_txn_resp", "Starbucks", 550, "2025-03-20")

	result, err := svc.ListTransactions(ctx, service.TransactionListParams{Limit: 10})
	if err != nil {
		t.Fatalf("ListTransactions: %v", err)
	}
	if len(result.Transactions) != 1 {
		t.Fatalf("expected 1 transaction, got %d", len(result.Transactions))
	}
	if result.Transactions[0].ShortID != dbTxn.ShortID {
		t.Fatalf("ShortID mismatch: response=%q, db=%q", result.Transactions[0].ShortID, dbTxn.ShortID)
	}
}

// --- Resolver: accept both UUID and short_id ---

func TestShortID_Resolver_GetAccount_ByUUID(t *testing.T) {
	svc, queries, _ := newService(t)
	ctx := context.Background()

	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_resolve_conn")
	dbAcct := testutil.MustCreateAccount(t, queries, conn.ID, "sid_resolve_acct", "Checking")

	uuid := pgconv.FormatUUID(dbAcct.ID)
	acct, err := svc.GetAccount(ctx, uuid)
	if err != nil {
		t.Fatalf("GetAccount by UUID: %v", err)
	}
	if acct.ShortID != dbAcct.ShortID {
		t.Fatalf("expected ShortID %q, got %q", dbAcct.ShortID, acct.ShortID)
	}
}

func TestShortID_Resolver_GetAccount_ByShortID(t *testing.T) {
	svc, queries, _ := newService(t)
	ctx := context.Background()

	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_resolve2_conn")
	dbAcct := testutil.MustCreateAccount(t, queries, conn.ID, "sid_resolve2_acct", "Savings")

	acct, err := svc.GetAccount(ctx, dbAcct.ShortID)
	if err != nil {
		t.Fatalf("GetAccount by short_id %q: %v", dbAcct.ShortID, err)
	}
	if acct.ID != pgconv.FormatUUID(dbAcct.ID) {
		t.Fatalf("expected UUID %q, got %q", pgconv.FormatUUID(dbAcct.ID), acct.ID)
	}
}

func TestShortID_Resolver_GetTransaction_ByShortID(t *testing.T) {
	svc, queries, _ := newService(t)
	ctx := context.Background()

	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_txn_resolve_conn")
	acct := testutil.MustCreateAccount(t, queries, conn.ID, "sid_txn_resolve_acct", "Checking")
	dbTxn := testutil.MustCreateTransaction(t, queries, acct.ID, "sid_txn_resolve", "Grocery Store", 2500, "2025-04-01")

	txn, err := svc.GetTransaction(ctx, dbTxn.ShortID)
	if err != nil {
		t.Fatalf("GetTransaction by short_id %q: %v", dbTxn.ShortID, err)
	}
	if txn.ID != pgconv.FormatUUID(dbTxn.ID) {
		t.Fatalf("expected UUID %q, got %q", pgconv.FormatUUID(dbTxn.ID), txn.ID)
	}
	if txn.ShortID != dbTxn.ShortID {
		t.Fatalf("expected ShortID %q, got %q", dbTxn.ShortID, txn.ShortID)
	}
}

func TestShortID_Resolver_ListTransactions_FilterByUserShortID(t *testing.T) {
	svc, queries, _ := newService(t)
	ctx := context.Background()

	alice := testutil.MustCreateUser(t, queries, "Alice")
	bob := testutil.MustCreateUser(t, queries, "Bob")
	connA := testutil.MustCreateConnection(t, queries, alice.ID, "sid_filter_connA")
	connB := testutil.MustCreateConnection(t, queries, bob.ID, "sid_filter_connB")
	acctA := testutil.MustCreateAccount(t, queries, connA.ID, "sid_filter_acctA", "Alice Checking")
	acctB := testutil.MustCreateAccount(t, queries, connB.ID, "sid_filter_acctB", "Bob Checking")
	testutil.MustCreateTransaction(t, queries, acctA.ID, "sid_filter_txnA", "Alice Purchase", 1000, "2025-03-15")
	testutil.MustCreateTransaction(t, queries, acctB.ID, "sid_filter_txnB", "Bob Purchase", 2000, "2025-03-15")

	// Filter by Alice's short_id
	aliceShortID := alice.ShortID
	result, err := svc.ListTransactions(ctx, service.TransactionListParams{
		UserID: &aliceShortID,
		Limit:  50,
	})
	if err != nil {
		t.Fatalf("ListTransactions with user short_id: %v", err)
	}
	if len(result.Transactions) != 1 {
		t.Fatalf("expected 1 transaction for Alice, got %d", len(result.Transactions))
	}
	if result.Transactions[0].ProviderName != "Alice Purchase" {
		t.Fatalf("expected 'Alice Purchase', got %q", result.Transactions[0].ProviderName)
	}
}

func TestShortID_Resolver_ListTransactions_FilterByAccountShortID(t *testing.T) {
	svc, queries, _ := newService(t)
	ctx := context.Background()

	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_acctfilter_conn")
	checking := testutil.MustCreateAccount(t, queries, conn.ID, "sid_acctfilter_chk", "Checking")
	savings := testutil.MustCreateAccount(t, queries, conn.ID, "sid_acctfilter_sav", "Savings")
	testutil.MustCreateTransaction(t, queries, checking.ID, "sid_acctfilter_txn1", "Check Purchase", 500, "2025-03-15")
	testutil.MustCreateTransaction(t, queries, savings.ID, "sid_acctfilter_txn2", "Savings Deposit", -1000, "2025-03-15")

	checkingShortID := checking.ShortID
	result, err := svc.ListTransactions(ctx, service.TransactionListParams{
		AccountID: &checkingShortID,
		Limit:     50,
	})
	if err != nil {
		t.Fatalf("ListTransactions with account short_id: %v", err)
	}
	if len(result.Transactions) != 1 {
		t.Fatalf("expected 1 transaction for Checking, got %d", len(result.Transactions))
	}
	if result.Transactions[0].ProviderName != "Check Purchase" {
		t.Fatalf("expected 'Check Purchase', got %q", result.Transactions[0].ProviderName)
	}
}

func TestShortID_Resolver_NotFound(t *testing.T) {
	svc, _, _ := newService(t)
	ctx := context.Background()

	// Non-existent short_id should return ErrNotFound
	_, err := svc.GetAccount(ctx, "ZZZZZZZZ")
	if err == nil {
		t.Fatal("expected error for non-existent short_id")
	}

	// Non-existent UUID should also return ErrNotFound
	_, err = svc.GetAccount(ctx, "00000000-0000-0000-0000-000000000000")
	if err == nil {
		t.Fatal("expected error for non-existent UUID")
	}
}

// --- Field filtering includes short_id ---

func TestShortID_AlwaysIncludedInFieldSelection(t *testing.T) {
	fields, err := service.ParseFields("provider_name,amount")
	if err != nil {
		t.Fatalf("ParseFields: %v", err)
	}
	if !fields["short_id"] {
		t.Fatal("expected short_id to be always included in parsed fields")
	}
	if !fields["id"] {
		t.Fatal("expected id to be always included in parsed fields")
	}
}

func TestShortID_FilterTransactionFields(t *testing.T) {
	txn := service.TransactionResponse{
		ID:           "some-uuid",
		ShortID:      "Ab12Cd34",
		ProviderName: "Coffee",
		Amount:       4.50,
	}

	fields := map[string]bool{
		"id":            true,
		"short_id":      true,
		"provider_name": true,
	}

	filtered := service.FilterTransactionFields(txn, fields)
	if filtered["short_id"] != "Ab12Cd34" {
		t.Fatalf("expected short_id in filtered output, got %v", filtered["short_id"])
	}
	if filtered["id"] != "some-uuid" {
		t.Fatalf("expected id in filtered output, got %v", filtered["id"])
	}
}

// --- Short ID on category response ---

func TestShortID_InCategoryResponse(t *testing.T) {
	svc, _, pool := newService(t)
	ctx := context.Background()

	// Insert a category directly.
	var catShortID string
	err := pool.QueryRow(ctx,
		"INSERT INTO categories (slug, display_name) VALUES ('test_short_id_cat', 'Test Category') RETURNING short_id",
	).Scan(&catShortID)
	if err != nil {
		t.Fatalf("insert category: %v", err)
	}
	if !shortid.IsShortID(catShortID) {
		t.Fatalf("category short_id %q is not valid", catShortID)
	}

	cat, err := svc.GetCategoryBySlug(ctx, "test_short_id_cat")
	if err != nil {
		t.Fatalf("GetCategoryBySlug: %v", err)
	}
	if cat.ShortID != catShortID {
		t.Fatalf("expected ShortID %q, got %q", catShortID, cat.ShortID)
	}
}

// --- Short ID on connection response ---

func TestShortID_InConnectionResponse(t *testing.T) {
	svc, queries, _ := newService(t)
	ctx := context.Background()

	user := testutil.MustCreateUser(t, queries, "Alice")
	dbConn := testutil.MustCreateConnection(t, queries, user.ID, "sid_conn_resp")

	conns, err := svc.ListConnections(ctx, nil)
	if err != nil {
		t.Fatalf("ListConnections: %v", err)
	}
	if len(conns) != 1 {
		t.Fatalf("expected 1 connection, got %d", len(conns))
	}
	if conns[0].ShortID != dbConn.ShortID {
		t.Fatalf("ShortID mismatch: response=%q, db=%q", conns[0].ShortID, dbConn.ShortID)
	}
}

// --- SetTransactionCategory with short IDs ---

func TestShortID_SetTransactionCategory_WithShortIDs(t *testing.T) {
	svc, queries, pool := newService(t)
	ctx := context.Background()

	user := testutil.MustCreateUser(t, queries, "Alice")
	conn := testutil.MustCreateConnection(t, queries, user.ID, "sid_cat_conn")
	acct := testutil.MustCreateAccount(t, queries, conn.ID, "sid_cat_acct", "Checking")
	dbTxn := testutil.MustCreateTransaction(t, queries, acct.ID, "sid_cat_txn", "Restaurant", 3500, "2025-04-01")

	// Create a category.
	var catID pgtype.UUID
	var catShortID string
	err := pool.QueryRow(ctx,
		"INSERT INTO categories (slug, display_name) VALUES ('sid_test_food', 'Food') RETURNING id, short_id",
	).Scan(&catID, &catShortID)
	if err != nil {
		t.Fatalf("insert category: %v", err)
	}

	// Categorize using short_ids for both transaction and category.
	err = svc.SetTransactionCategory(ctx, dbTxn.ShortID, catShortID, service.SystemActor())
	if err != nil {
		t.Fatalf("SetTransactionCategory with short_ids: %v", err)
	}

	// Verify it worked.
	txn, err := svc.GetTransaction(ctx, dbTxn.ShortID)
	if err != nil {
		t.Fatalf("GetTransaction: %v", err)
	}
	if txn.Category == nil {
		t.Fatal("expected category to be set")
	}
	if *txn.Category.Slug != "sid_test_food" {
		t.Fatalf("expected category slug 'sid_test_food', got %q", *txn.Category.Slug)
	}
}
