package store

import (
	"context"
	"testing"
	"time"

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

func TestPostgresPoolConfigSetsDiagnosticAndConnectTimeout(t *testing.T) {
	config, err := postgresPoolConfig("postgresql://gateway:password@localhost:5432/gateway?sslmode=disable")
	if err != nil {
		t.Fatalf("parse PostgreSQL pool config: %v", err)
	}
	if config.ConnConfig.ConnectTimeout != 5*time.Second {
		t.Fatalf("connect timeout = %s, want 5s", config.ConnConfig.ConnectTimeout)
	}
	if applicationName := config.ConnConfig.RuntimeParams["application_name"]; applicationName != "easyai-ai-gateway" {
		t.Fatalf("application_name = %q, want easyai-ai-gateway", applicationName)
	}
}

func TestPostgresPoolConfigRejectsMalformedURL(t *testing.T) {
	if _, err := postgresPoolConfig("://malformed"); err == nil {
		t.Fatal("expected malformed PostgreSQL URL to fail")
	}
}

func TestPostgresPoolConfigUsesExplicitMinimumIdleConnections(t *testing.T) {
	config, err := postgresPoolConfig(
		"postgresql://gateway:password@localhost:5432/gateway?sslmode=disable",
		24,
	)
	if err != nil {
		t.Fatalf("parse PostgreSQL pool config: %v", err)
	}
	if config.MaxConns != 24 || config.MinIdleConns != 0 {
		t.Fatalf(
			"pool bounds max=%d minIdle=%d, want 24/0",
			config.MaxConns,
			config.MinIdleConns,
		)
	}

	warmed, err := postgresPoolConfigWithOptions(
		"postgresql://gateway:password@localhost:5432/gateway?sslmode=disable",
		PostgresPoolOptions{MaxConns: 24, MinIdleConns: 4},
	)
	if err != nil {
		t.Fatalf("parse warmed PostgreSQL pool config: %v", err)
	}
	if warmed.MinIdleConns != 4 {
		t.Fatalf("warmed pool minIdle=%d, want 4", warmed.MinIdleConns)
	}
}

func TestPostgresPoolConfigSetsBoundedTransactionTimeouts(t *testing.T) {
	config, err := postgresPoolConfigWithOptions(
		"postgresql://gateway:password@localhost:5432/gateway?sslmode=disable",
		PostgresPoolOptions{
			MaxConns:                 32,
			MinIdleConns:             4,
			MaxConnIdleTime:          30 * time.Second,
			IdleInTransactionTimeout: 60 * time.Second,
			LockTimeout:              30 * time.Second,
		},
	)
	if err != nil {
		t.Fatalf("parse PostgreSQL pool config: %v", err)
	}
	if got := config.ConnConfig.RuntimeParams["idle_in_transaction_session_timeout"]; got != "60000ms" {
		t.Fatalf("idle transaction timeout = %q, want 60000ms", got)
	}
	if got := config.ConnConfig.RuntimeParams["lock_timeout"]; got != "30000ms" {
		t.Fatalf("lock timeout = %q, want 30000ms", got)
	}
	if config.MaxConns != 32 || config.MinIdleConns != 4 {
		t.Fatalf("pool bounds max=%d minIdle=%d, want 32/4", config.MaxConns, config.MinIdleConns)
	}
	if config.MaxConnIdleTime != 30*time.Second {
		t.Fatalf("pool max idle time=%s, want 30s", config.MaxConnIdleTime)
	}
}

func TestIsPostgresUnavailableClassifiesConnectivityFailures(t *testing.T) {
	for _, testCase := range []struct {
		name string
		err  error
	}{
		{name: "deadline", err: context.DeadlineExceeded},
		{name: "connection exception", err: &pgconn.PgError{Code: "08006"}},
		{name: "too many connections", err: &pgconn.PgError{Code: "53300"}},
		{name: "cannot connect now", err: &pgconn.PgError{Code: "57P03"}},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			if !IsPostgresUnavailable(testCase.err) {
				t.Fatalf("error %v was not classified as PostgreSQL unavailable", testCase.err)
			}
		})
	}
	if IsPostgresUnavailable(&pgconn.PgError{Code: "42601"}) {
		t.Fatal("SQL syntax error was incorrectly classified as PostgreSQL unavailable")
	}
}

func TestIsPostgresLockTimeout(t *testing.T) {
	if !IsPostgresLockTimeout(&pgconn.PgError{Code: "55P03"}) {
		t.Fatal("lock timeout was not classified")
	}
	if IsPostgresLockTimeout(&pgconn.PgError{Code: "57014"}) {
		t.Fatal("query cancellation was classified as lock timeout")
	}
}
