package repositories

import (
	"testing"
	"time"

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

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

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

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

	found, err := repo.FindByUserID(user.ID)
	require.NoError(t, err)
	assert.Equal(t, models.TierPro, found.Tier)
}

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

	_, err := repo.FindByUserID(9999)
	assert.Error(t, err)
}

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

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	sub, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	sub.Tier = models.TierPro
	err = repo.Update(sub)
	require.NoError(t, err)

	found, err := repo.FindByUserID(user.ID)
	require.NoError(t, err)
	assert.Equal(t, models.TierPro, found.Tier)
}

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

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	_, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	expiresAt := time.Now().UTC().AddDate(1, 0, 0)
	err = repo.UpgradeToPro(user.ID, expiresAt, "apple")
	require.NoError(t, err)

	found, err := repo.FindByUserID(user.ID)
	require.NoError(t, err)
	assert.Equal(t, models.TierPro, found.Tier)
	assert.Equal(t, "apple", found.Platform)
	assert.True(t, found.AutoRenew)
	require.NotNil(t, found.SubscribedAt)
	require.NotNil(t, found.ExpiresAt)
}

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

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	_, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	// First upgrade
	err = repo.UpgradeToPro(user.ID, time.Now().UTC().AddDate(1, 0, 0), "apple")
	require.NoError(t, err)

	// Then downgrade
	err = repo.DowngradeToFree(user.ID)
	require.NoError(t, err)

	found, err := repo.FindByUserID(user.ID)
	require.NoError(t, err)
	assert.Equal(t, models.TierFree, found.Tier)
	assert.False(t, found.AutoRenew)
	require.NotNil(t, found.CancelledAt)
}

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

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	_, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	err = repo.SetAutoRenew(user.ID, false)
	require.NoError(t, err)

	found, err := repo.FindByUserID(user.ID)
	require.NoError(t, err)
	assert.False(t, found.AutoRenew)
}

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

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	_, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	err = repo.UpdateReceiptData(user.ID, "receipt_data_abc123")
	require.NoError(t, err)

	var sub models.UserSubscription
	db.Where("user_id = ?", user.ID).First(&sub)
	require.NotNil(t, sub.AppleReceiptData)
	assert.Equal(t, "receipt_data_abc123", *sub.AppleReceiptData)
}

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

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	_, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	err = repo.UpdatePurchaseToken(user.ID, "google_token_xyz")
	require.NoError(t, err)

	var sub models.UserSubscription
	db.Where("user_id = ?", user.ID).First(&sub)
	require.NotNil(t, sub.GooglePurchaseToken)
	assert.Equal(t, "google_token_xyz", *sub.GooglePurchaseToken)
}

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

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	// Use raw SQL to ensure apple_receipt_data is stored correctly (GORM may omit pointer fields)
	require.NoError(t, db.Exec(
		"INSERT INTO subscription_usersubscription (user_id, tier, apple_receipt_data, created_at, updated_at) VALUES (?, ?, ?, datetime('now'), datetime('now'))",
		user.ID, models.TierPro, "transactionid=txnabc123data",
	).Error)

	// Avoid underscores in search term: escapeLikeWildcards escapes _ with \
	// which PostgreSQL handles but SQLite does not (no default ESCAPE clause)
	found, err := repo.FindByAppleReceiptContains("txnabc123")
	require.NoError(t, err)
	assert.Equal(t, user.ID, found.UserID)
}

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

	_, err := repo.FindByAppleReceiptContains("nonexistent_txn")
	assert.Error(t, err)
}

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

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

	found, err := repo.FindByGoogleToken("google_purchase_abc")
	require.NoError(t, err)
	assert.Equal(t, user.ID, found.UserID)
}

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

	_, err := repo.FindByGoogleToken("nonexistent_token")
	assert.Error(t, err)
}

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

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	_, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	cancelTime := time.Now().UTC()
	err = repo.SetCancelledAt(user.ID, cancelTime)
	require.NoError(t, err)

	found, err := repo.FindByUserID(user.ID)
	require.NoError(t, err)
	require.NotNil(t, found.CancelledAt)
}

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

	user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
	_, err := repo.GetOrCreate(user.ID)
	require.NoError(t, err)

	// Set then clear
	err = repo.SetCancelledAt(user.ID, time.Now().UTC())
	require.NoError(t, err)

	err = repo.ClearCancelledAt(user.ID)
	require.NoError(t, err)

	found, err := repo.FindByUserID(user.ID)
	require.NoError(t, err)
	assert.Nil(t, found.CancelledAt)
}

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

	// No tier limits in DB, should return defaults
	freeLimits, err := repo.GetTierLimits(models.TierFree)
	require.NoError(t, err)
	assert.NotNil(t, freeLimits)

	proLimits, err := repo.GetTierLimits(models.TierPro)
	require.NoError(t, err)
	assert.NotNil(t, proLimits)
}

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

	// No limits seeded = empty
	limits, err := repo.GetAllTierLimits()
	require.NoError(t, err)
	assert.Empty(t, limits)
}

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

	// No settings in DB, should return default (limitations disabled)
	settings, err := repo.GetSettings()
	require.NoError(t, err)
	assert.NotNil(t, settings)
	assert.False(t, settings.EnableLimitations)
}

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

	trigger := &models.UpgradeTrigger{
		TriggerKey: "max_residences",
		Title:      "Upgrade for more homes",
		Message:    "Free tier supports 1 home",
		IsActive:   true,
	}
	require.NoError(t, db.Create(trigger).Error)

	found, err := repo.GetUpgradeTrigger("max_residences")
	require.NoError(t, err)
	assert.Equal(t, "max_residences", found.TriggerKey)
}

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

	_, err := repo.GetUpgradeTrigger("nonexistent_key")
	assert.Error(t, err)
}

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

	trigger1 := &models.UpgradeTrigger{TriggerKey: "key1", Title: "T1", Message: "M1", IsActive: true}
	trigger2 := &models.UpgradeTrigger{TriggerKey: "key2", Title: "T2", Message: "M2", IsActive: true}
	require.NoError(t, db.Create(trigger1).Error)
	require.NoError(t, db.Create(trigger2).Error)
	// Use raw SQL for inactive record to avoid GORM default:true overriding IsActive=false
	require.NoError(t, db.Exec(
		"INSERT INTO subscription_upgradetrigger (trigger_key, title, message, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))",
		"key3", "T3", "M3", false,
	).Error)

	triggers, err := repo.GetAllUpgradeTriggers()
	require.NoError(t, err)
	assert.Len(t, triggers, 2) // Only active
}

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

	b1 := &models.FeatureBenefit{FeatureName: "Benefit 1", FreeTierText: "1 home", ProTierText: "Unlimited", DisplayOrder: 1, IsActive: true}
	require.NoError(t, db.Create(b1).Error)
	// Use raw SQL for inactive record to avoid GORM default:true overriding IsActive=false
	require.NoError(t, db.Exec(
		"INSERT INTO subscription_featurebenefit (feature_name, free_tier_text, pro_tier_text, display_order, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
		"Benefit 2", "3 tasks", "Unlimited", 2, false,
	).Error)

	benefits, err := repo.GetFeatureBenefits()
	require.NoError(t, err)
	assert.Len(t, benefits, 1) // Only active
}

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

	now := time.Now().UTC()
	active := &models.Promotion{
		PromotionID: "promo_active",
		TargetTier:  models.TierPro,
		Title:       "Active Promo",
		Message:     "Get Pro now!",
		StartDate:   now.AddDate(0, 0, -1),
		EndDate:     now.AddDate(0, 0, 1),
		IsActive:    true,
	}
	expired := &models.Promotion{
		PromotionID: "promo_expired",
		TargetTier:  models.TierPro,
		Title:       "Expired Promo",
		Message:     "Too late!",
		StartDate:   now.AddDate(0, 0, -10),
		EndDate:     now.AddDate(0, 0, -5),
		IsActive:    true,
	}
	require.NoError(t, db.Create(active).Error)
	require.NoError(t, db.Create(expired).Error)

	promos, err := repo.GetActivePromotions(models.TierPro)
	require.NoError(t, err)
	assert.Len(t, promos, 1)
	assert.Equal(t, "promo_active", promos[0].PromotionID)
}

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

	promo := &models.Promotion{
		PromotionID: "promo_find",
		TargetTier:  models.TierPro,
		Title:       "Find Me",
		Message:     "Found!",
		StartDate:   time.Now().UTC(),
		EndDate:     time.Now().UTC().AddDate(0, 0, 30),
		IsActive:    true,
	}
	require.NoError(t, db.Create(promo).Error)

	found, err := repo.GetPromotionByID("promo_find")
	require.NoError(t, err)
	assert.Equal(t, "Find Me", found.Title)
}

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

	_, err := repo.GetPromotionByID("nonexistent")
	assert.Error(t, err)
}

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

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

	found, err := repo.FindByStripeSubscriptionID("sub_test_123")
	require.NoError(t, err)
	assert.Equal(t, user.ID, found.UserID)
}

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

	_, err := repo.FindByStripeSubscriptionID("sub_nonexistent")
	assert.Error(t, err)
}
