package repositories

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"

	"github.com/treytartt/honeydue-api/internal/models"
	"github.com/treytartt/honeydue-api/internal/testutil"
)

func TestGetOrCreate_New_CreatesFreeTier(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewSubscriptionRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")

	sub, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)
	require.NotNil(t, sub)

	assert.Equal(t, user.ID, sub.UserID)
	assert.Equal(t, models.TierFree, sub.Tier)
	assert.True(t, sub.AutoRenew)

	// Verify persisted
	var count int64
	db.Model(&models.UserSubscription{}).Where("user_id = ?", user.ID).Count(&count)
	assert.Equal(t, int64(1), count, "should have exactly one subscription record")
}

func TestGetOrCreate_AlreadyExists_Returns(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewSubscriptionRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")

	// Create a pro subscription manually
	existing := &models.UserSubscription{
		UserID:    user.ID,
		Tier:      models.TierPro,
		AutoRenew: true,
	}
	err := db.Create(existing).Error
	require.NoError(t, err)

	// GetOrCreate should return existing, not overwrite with free defaults
	sub, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)
	require.NotNil(t, sub)

	assert.Equal(t, existing.ID, sub.ID, "should return the existing record by ID")
	assert.Equal(t, models.TierPro, sub.Tier, "should preserve existing pro tier, not overwrite with free")

	// Verify still only one record
	var count int64
	db.Model(&models.UserSubscription{}).Where("user_id = ?", user.ID).Count(&count)
	assert.Equal(t, int64(1), count, "should still have exactly one subscription record")
}

func TestGetOrCreate_Idempotent(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewSubscriptionRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")

	sub1, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	sub2, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	assert.Equal(t, sub1.ID, sub2.ID)

	var count int64
	db.Model(&models.UserSubscription{}).Where("user_id = ?", user.ID).Count(&count)
	assert.Equal(t, int64(1), count, "should have exactly one subscription record after two calls")
}

func TestFindByStripeCustomerID(t *testing.T) {
	tests := []struct {
		name       string
		customerID string
		seedID     string
		wantErr    bool
	}{
		{
			name:       "finds existing subscription by stripe customer ID",
			customerID: "cus_test123",
			seedID:     "cus_test123",
			wantErr:    false,
		},
		{
			name:       "returns error for unknown stripe customer ID",
			customerID: "cus_unknown999",
			seedID:     "cus_test456",
			wantErr:    true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			db := testutil.SetupTestDB(t)
			repo := NewSubscriptionRepository(db)

			user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
			sub := &models.UserSubscription{
				UserID:           user.ID,
				Tier:             models.TierFree,
				StripeCustomerID: &tt.seedID,
			}
			err := db.Create(sub).Error
			require.NoError(t, err)

			found, err := repo.FindByStripeCustomerID(tt.customerID)
			if tt.wantErr {
				assert.Error(t, err)
				assert.ErrorIs(t, err, gorm.ErrRecordNotFound)
			} else {
				require.NoError(t, err)
				require.NotNil(t, found)
				assert.Equal(t, user.ID, found.UserID)
				assert.Equal(t, tt.seedID, *found.StripeCustomerID)
			}
		})
	}
}

func TestUpdateStripeData(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewSubscriptionRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	sub := &models.UserSubscription{
		UserID: user.ID,
		Tier:   models.TierFree,
	}
	err := db.Create(sub).Error
	require.NoError(t, err)

	// Update all three Stripe fields
	customerID := "cus_abc123"
	subscriptionID := "sub_xyz789"
	priceID := "price_monthly"

	err = repo.UpdateStripeData(user.ID, customerID, subscriptionID, priceID)
	require.NoError(t, err)

	// Verify all three fields are set
	var updated models.UserSubscription
	err = db.Where("user_id = ?", user.ID).First(&updated).Error
	require.NoError(t, err)
	require.NotNil(t, updated.StripeCustomerID)
	require.NotNil(t, updated.StripeSubscriptionID)
	require.NotNil(t, updated.StripePriceID)
	assert.Equal(t, customerID, *updated.StripeCustomerID)
	assert.Equal(t, subscriptionID, *updated.StripeSubscriptionID)
	assert.Equal(t, priceID, *updated.StripePriceID)

	// Now call ClearStripeData
	err = repo.ClearStripeData(user.ID)
	require.NoError(t, err)

	// Verify subscription_id and price_id are cleared, customer_id preserved
	var cleared models.UserSubscription
	err = db.Where("user_id = ?", user.ID).First(&cleared).Error
	require.NoError(t, err)
	require.NotNil(t, cleared.StripeCustomerID, "customer_id should be preserved after ClearStripeData")
	assert.Equal(t, customerID, *cleared.StripeCustomerID)
	assert.Nil(t, cleared.StripeSubscriptionID, "subscription_id should be nil after ClearStripeData")
	assert.Nil(t, cleared.StripePriceID, "price_id should be nil after ClearStripeData")
}

func TestSetTrialDates(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewSubscriptionRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	sub := &models.UserSubscription{
		UserID:    user.ID,
		Tier:      models.TierFree,
		TrialUsed: false,
	}
	err := db.Create(sub).Error
	require.NoError(t, err)

	trialStart := time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)
	trialEnd := time.Date(2026, 3, 15, 0, 0, 0, 0, time.UTC)

	err = repo.SetTrialDates(user.ID, trialStart, trialEnd)
	require.NoError(t, err)

	var updated models.UserSubscription
	err = db.Where("user_id = ?", user.ID).First(&updated).Error
	require.NoError(t, err)

	require.NotNil(t, updated.TrialStart)
	require.NotNil(t, updated.TrialEnd)
	assert.True(t, updated.TrialUsed, "trial_used should be set to true")
	assert.WithinDuration(t, trialStart, *updated.TrialStart, time.Second, "trial_start should match")
	assert.WithinDuration(t, trialEnd, *updated.TrialEnd, time.Second, "trial_end should match")
}

func TestUpdateExpiresAt(t *testing.T) {
	db := testutil.SetupTestDB(t)
	repo := NewSubscriptionRepository(db)

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "password")
	sub := &models.UserSubscription{
		UserID: user.ID,
		Tier:   models.TierPro,
	}
	err := db.Create(sub).Error
	require.NoError(t, err)

	newExpiry := time.Date(2027, 6, 15, 12, 0, 0, 0, time.UTC)
	err = repo.UpdateExpiresAt(user.ID, newExpiry)
	require.NoError(t, err)

	var updated models.UserSubscription
	err = db.Where("user_id = ?", user.ID).First(&updated).Error
	require.NoError(t, err)

	require.NotNil(t, updated.ExpiresAt)
	assert.WithinDuration(t, newExpiry, *updated.ExpiresAt, time.Second, "expires_at should be updated")
}
