package datastore

import (
	"context"
	"fmt"
	"testing"
	"time"

	gpostgres "gorm.io/driver/postgres"
	"gorm.io/gorm"

	"github.com/docker/go-connections/nat"
	"github.com/google/uuid"

	_ "github.com/lib/pq"

	"github.com/testcontainers/testcontainers-go"
	tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres"
	"github.com/testcontainers/testcontainers-go/wait"

	"github.com/Giorgio-Abboud/Green-Vault/internal/package/models"
)

func newTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	ctx := context.Background()

	// Start Postgres container
	pg, err := tcpostgres.Run(
		ctx,
		"postgres:16",
		tcpostgres.WithDatabase("testdb"),
		tcpostgres.WithUsername("test"),
		tcpostgres.WithPassword("test"),
		testcontainers.WithWaitStrategy(
			wait.ForSQL("5432/tcp", "postgres",
				func(host string, port nat.Port) string {
					return fmt.Sprintf(
						"host=%s port=%s user=test password=test dbname=testdb sslmode=disable",
						host, port.Port(),
					)
				},
			).WithStartupTimeout(20*time.Second),
		),
	)
	if err != nil {
		t.Fatalf("failed to start postgres container: %v", err)
	}

	t.Cleanup(func() { _ = pg.Terminate(ctx) })

	// Connection string
	connStr, err := pg.ConnectionString(ctx, "sslmode=disable")
	if err != nil {
		t.Fatalf("failed to get connection string: %v", err)
	}

	// Connect with GORM
	db, err := gorm.Open(gpostgres.Open(connStr), &gorm.Config{})
	if err != nil {
		t.Fatalf("failed to connect to postgres: %v", err)
	}

	// Run migrations
	if err := db.AutoMigrate(
		&models.User{},
		&models.UserFill{},
		&models.Metric{},
	); err != nil {
		t.Fatalf("failed to migrate test db: %v", err)
	}

	return db
}

func newStore(t *testing.T) *Store {
	t.Helper()
	return New(newTestDB(t))
}

/************ CreateUser ************/

func TestCreateUser(t *testing.T) {
	store := newStore(t)
	ctx := context.Background()

	for _, tc := range []struct {
		name    string
		user    models.User
		wantErr bool
	}{
		{
			name: "ok",
			user: models.User{
				ID:           uuid.New(),
				Email:        "ok@example.com",
				Name:         "Ok",
				LastName:     "User",
				PasswordHash: "hashed",
			},
			wantErr: false,
		},
		{
			name: "missing email",
			user: models.User{
				ID:           uuid.New(),
				Email:        "",
				Name:         "X",
				LastName:     "Y",
				PasswordHash: "hashed",
			},
			wantErr: true,
		},
		{
			name: "missing id",
			user: models.User{
				ID:           uuid.Nil,
				Email:        "noid@example.com",
				Name:         "X",
				LastName:     "Y",
				PasswordHash: "hashed",
			},
			wantErr: true,
		},
		{
			name: "duplicate email",
			user: models.User{
				ID:           uuid.New(),
				Email:        "ok@example.com",
				Name:         "A",
				LastName:     "B",
				PasswordHash: "hashed",
			},
			wantErr: true,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			_, err := store.CreateUser(ctx, &tc.user)
			if (err != nil) != tc.wantErr {
				t.Fatalf("CreateUser err=%v wantErr=%v", err, tc.wantErr)
			}
		})
	}
}

/************ GetUserByEmail ************/

func TestGetUserByEmail(t *testing.T) {
	store := newStore(t)
	ctx := context.Background()

	u := models.User{
		ID:           uuid.New(),
		Email:        "findme@example.com",
		Name:         "Roary",
		LastName:     "Panther",
		PasswordHash: "hashed",
	}
	if _, err := store.CreateUser(ctx, &u); err != nil {
		t.Fatalf("seed CreateUser: %v", err)
	}

	for _, tc := range []struct {
		name     string
		email    string
		wantErr  bool
		wantSame bool
	}{
		{
			name:     "ok",
			email:    "findme@example.com",
			wantErr:  false,
			wantSame: true,
		},
		{
			name:     "empty email",
			email:    "",
			wantErr:  true,
			wantSame: false,
		},
		{
			name:     "not found",
			email:    "random@example.com",
			wantErr:  true,
			wantSame: false,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			got, err := store.GetUserByEmail(ctx, tc.email)
			if tc.wantErr {
				if err == nil {
					t.Fatal("expected error, got nil")
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if tc.wantSame && got.Email != u.Email {
				t.Fatalf("got %q want %q", got.Email, u.Email)
			}
		})
	}
}

/************ GetUserByID ************/

func TestGetUserByID(t *testing.T) {
	store := newStore(t)
	ctx := context.Background()

	idTest := uuid.New()
	u := models.User{
		ID:           idTest,
		Email:        "myemail@email.com",
		Name:         "Roary",
		LastName:     "Panther",
		PasswordHash: "hashed",
	}
	if _, err := store.CreateUser(ctx, &u); err != nil {
		t.Fatalf("seed CreateUser: %v", err)
	}

	for _, tc := range []struct {
		name     string
		id       uuid.UUID
		wantErr  bool
		wantSame bool
	}{
		{
			name:     "ok",
			id:       idTest,
			wantErr:  false,
			wantSame: true,
		},
		{
			name:    "empty uuid",
			id:      uuid.Nil,
			wantErr: true,
		},
		{
			name:    "not found",
			id:      uuid.New(),
			wantErr: true,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			got, err := store.GetUserByID(ctx, tc.id)
			if tc.wantErr {
				if err == nil {
					t.Fatal("expected error, got nil")
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if tc.wantSame && got.ID != u.ID {
				t.Fatalf("got %q want %q", got.Email, u.Email)
			}
		})
	}
}

/************ CreateUserFill ************/

func TestCreateUserFill(t *testing.T) {
	store := newStore(t)
	ctx := context.Background()

	// seed a user
	u := models.User{
		ID:           uuid.New(),
		Email:        "fills@example.com",
		Name:         "F",
		LastName:     "L",
		PasswordHash: "hashed",
	}
	if _, err := store.CreateUser(ctx, &u); err != nil {
		t.Fatalf("seed user: %v", err)
	}

	for _, tc := range []struct {
		name    string
		fill    models.UserFill
		wantErr bool
	}{
		{
			name: "ok",
			fill: models.UserFill{
				ID:        uuid.New(),
				UserID:    u.ID,
				Symbol:    "AAPL",
				Timestamp: time.Now().UTC(),
				Price:     100.5,
				Side:      "buy",
				Quantity:  10,
				Mode:      "Analyze",
				Result:    "SUCCESS",
			},
			wantErr: false,
		},
		{
			name: "missing fields",
			fill: models.UserFill{
				ID:     uuid.Nil,
				UserID: uuid.Nil,
				Symbol: "",
				Mode:   "",
			},
			wantErr: true,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			_, err := store.CreateUserFill(ctx, &tc.fill)
			if (err != nil) != tc.wantErr {
				t.Fatalf("CreateUserFill err=%v wantErr=%v", err, tc.wantErr)
			}
		})
	}
}

/************ CreateMetric ************/

func TestCreateMetric(t *testing.T) {
	store := newStore(t)
	ctx := context.Background()

	// seed user + fill
	u := models.User{
		ID:           uuid.New(),
		Email:        "metric@example.com",
		Name:         "Met",
		LastName:     "Ric",
		PasswordHash: "hashed",
	}
	if _, err := store.CreateUser(ctx, &u); err != nil {
		t.Fatalf("seed user: %v", err)
	}
	f := models.UserFill{
		ID:        uuid.New(),
		UserID:    u.ID,
		Symbol:    "TSLA",
		Timestamp: time.Now().UTC(),
		Price:     250.0,
		Side:      "sell",
		Quantity:  5,
		Mode:      "Estimate",
		Result:    "SUCCESS",
	}
	if _, err := store.CreateUserFill(ctx, &f); err != nil {
		t.Fatalf("seed fill: %v", err)
	}

	for _, tc := range []struct {
		name    string
		metric  models.Metric
		wantErr bool
	}{
		{
			name: "ok",
			metric: models.Metric{
				ID:              uuid.New(),
				UserFillID:      f.ID,
				VwapSlippage:    0.1,
				Shortfall:       0.02,
				EffectiveSpread: 0.01,
				RealizedSpread:  0.005,
				MarketImpact:    0.03,
				Drift:           0.01,
			},
			wantErr: false,
		},
		{
			name: "missing fields",
			metric: models.Metric{
				ID:         uuid.Nil,
				UserFillID: uuid.Nil,
			},
			wantErr: true,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			_, err := store.CreateMetric(ctx, &tc.metric)
			if (err != nil) != tc.wantErr {
				t.Fatalf("CreateMetric err=%v wantErr=%v", err, tc.wantErr)
			}
		})
	}
}

/************ UpdateUser ************/

func TestUpdateUser(t *testing.T) {
	store := newStore(t)
	ctx := context.Background()

	//Seed a test user
	idTest := uuid.New()
	u := models.User{
		ID:           idTest,
		Email:        "roary@testfiu.com",
		Name:         "Roary",
		LastName:     "Panther",
		PasswordHash: "hashed",
	}
	if _, err := store.CreateUser(ctx, &u); err != nil {
		t.Fatalf("seed CreateUser: %v", err)
	}

	for _, tc := range []struct {
		name       string
		id         uuid.UUID
		updateName string
		wantErr    bool
	}{
		{
			name:       "ok",
			id:         idTest,
			updateName: "TheRoary",
			wantErr:    false,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			u, err := store.GetUserByID(ctx, tc.id)
			u.Name = tc.updateName
			got, err := store.UpdateUser(ctx, u)

			if tc.wantErr {
				if err == nil {
					t.Fatal("expected error, got nil")
				}
				return
			}

			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			if got == nil {
				t.Fatal("expected deleted user, got nil")
			}

			if got.ID != tc.id {
				t.Fatalf("deleted user ID mismatch: got %v, want %v", got.ID, tc.id)
			}

			// Verify user updated
			updatedUser, err := store.GetUserByID(ctx, tc.id)
			if err != nil {
				t.Fatalf("no user was found after update")
			}
			if updatedUser.Name != tc.updateName {
				t.Fatalf("user was not updated")
			}
		})
	}
}

/************ DeleteUser ************/

func TestDeleteUser(t *testing.T) {
	store := newStore(t)
	ctx := context.Background()

	//Seed a test user
	idTest := uuid.New()
	u := models.User{
		ID:           idTest,
		Email:        "roary@testfiu.com",
		Name:         "Roary",
		LastName:     "Panther",
		PasswordHash: "hashed",
	}
	if _, err := store.CreateUser(ctx, &u); err != nil {
		t.Fatalf("seed CreateUser: %v", err)
	}

	// Seed a user fill tied to that user
	fill := models.UserFill{
		ID:        uuid.New(),
		UserID:    idTest,
		Symbol:    "AAPL",
		Price:     180.5,
		Side:      "buy",
		Mode:      "Analyze",
		Timestamp: time.Now().UTC(),
	}
	if _, err := store.CreateUserFill(ctx, &fill); err != nil {
		t.Fatalf("seed CreateUserFill: %v", err)
	}

	// Seed a metric tied to that user fill (not directly to user)
	metric := models.Metric{
		ID:              uuid.New(),
		UserFillID:      fill.ID,
		VwapSlippage:    0.1,
		Shortfall:       0.02,
		EffectiveSpread: 0.01,
		RealizedSpread:  0.005,
		MarketImpact:    0.03,
		Drift:           0.01,
	}
	if _, err := store.CreateMetric(ctx, &metric); err != nil {
		t.Fatalf("seed CreateMetric: %v", err)
	}

	for _, tc := range []struct {
		name    string
		id      uuid.UUID
		wantErr bool
	}{
		{
			name:    "ok",
			id:      idTest,
			wantErr: false,
		},
		{
			name:    "empty uuid",
			id:      uuid.Nil,
			wantErr: true,
		},
		{
			name:    "not found",
			id:      uuid.New(),
			wantErr: true,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			got, err := store.DeleteUser(ctx, tc.id)

			if tc.wantErr {
				if err == nil {
					t.Fatal("expected error, got nil")
				}
				return
			}

			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			if got == nil {
				t.Fatal("expected deleted user, got nil")
			}

			if got.ID != tc.id {
				t.Fatalf("deleted user ID mismatch: got %v, want %v", got.ID, tc.id)
			}

			// Verify user deleted
			_, err = store.GetUserByID(ctx, tc.id)
			if err == nil {
				t.Fatalf("expected user to be deleted, but still found")
			}

			// Verify all user fills for this user are deleted
			fills, err := store.ListUserFillsByUserID(ctx, tc.id)
			if err != nil {
				t.Fatalf("error checking user fills: %v", err)
			}
			if len(fills) != 0 {
				t.Fatalf("expected all user fills to be deleted, found %d", len(fills))
			}

			// Verify all metrics tied to the user's fills are deleted
			var metrics []models.Metric
			if err := store.DB.WithContext(ctx).Find(&metrics).Error; err != nil {
				t.Fatalf("error querying metrics: %v", err)
			}
			for _, m := range metrics {
				if m.UserFillID == fill.ID {
					t.Fatalf("expected metrics for fill %v to be deleted, but found one", fill.ID)
				}
			}
		})
	}
}

/************ ListUserFillsWithMetrics ************/

func TestListUserFillsWithMetrics(t *testing.T) {
	store := newStore(t)
	ctx := context.Background()

	//Seed a test user
	idTest := uuid.New()
	u := models.User{
		ID:           idTest,
		Email:        "metric@test.com",
		Name:         "Fill",
		LastName:     "Metric",
		PasswordHash: "hashed",
	}
	if _, err := store.CreateUser(ctx, &u); err != nil {
		t.Fatalf("seed CreateUser: %v", err)
	}

	// Seed a user fill tied to that user
	fill := models.UserFill{
		ID:        uuid.New(),
		UserID:    idTest,
		Symbol:    "AAPL",
		Price:     180.5,
		Side:      "buy",
		Mode:      "Analyze",
		Timestamp: time.Now().UTC().Truncate(time.Second),
	}
	if _, err := store.CreateUserFill(ctx, &fill); err != nil {
		t.Fatalf("seed CreateUserFill: %v", err)
	}

	// Seed a metric tied to that user fill (not directly to user)
	metric := models.Metric{
		ID:              uuid.New(),
		UserFillID:      fill.ID,
		VwapSlippage:    0.1,
		Shortfall:       0.02,
		EffectiveSpread: 0.01,
		RealizedSpread:  0.005,
		MarketImpact:    0.03,
		Drift:           0.01,
	}
	if _, err := store.CreateMetric(ctx, &metric); err != nil {
		t.Fatalf("seed CreateMetric: %v", err)
	}

	tests := []struct {
		name      string
		userID    uuid.UUID
		wantCount int
		wantErr   bool
	}{
		{
			name:      "ok - returns fills with metric",
			userID:    u.ID,
			wantCount: 1,
			wantErr:   false,
		},
		{
			name:      "ok - user has no fills",
			userID:    uuid.New(),
			wantCount: 0,
			wantErr:   false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			fills, err := store.ListUserFillsWithMetrics(ctx, tc.userID, "")

			if (err != nil) != tc.wantErr {
				t.Fatalf("ListUserFillsWithMetrics err=%v wantErr=%v", err, tc.wantErr)
			}

			if len(fills) != tc.wantCount {
				t.Fatalf("got %d fills, want %d", len(fills), tc.wantCount)
			}

			if tc.wantCount == 1 {
				if fills[0].Metric.ID == uuid.Nil {
					t.Fatalf("expected metric loaded, got nil metric")
				}
			}
		})
	}
}
