package userbehavior

import (
	"context"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	tntent "github.com/tidbcloud/devops/entity/tenant"
	ubmod "github.com/tidbcloud/devops/models/userbehavior"
	"github.com/tidbcloud/devops/pkg/infra/config"
	tenantsvc "github.com/tidbcloud/devops/pkg/service/tenant"
	"go.uber.org/mock/gomock"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

var _ = Describe("tenant event capture", func() {
	newCaptureDB := func() *gorm.DB {
		db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
		Expect(err).ToNot(HaveOccurred())
		sqlDB, err := db.DB()
		Expect(err).ToNot(HaveOccurred())
		sqlDB.SetMaxOpenConns(1)
		Expect(db.AutoMigrate(&ubmod.TenantEventCapture{})).To(Succeed())
		return db
	}

	eligibleTenant := func() *tntent.Tenant {
		return &tntent.Tenant{
			Id:               123,
			Name:             "Acme",
			Country:          "DE",
			Status:           tntent.TenantStatusActive,
			Kind:             tntent.TenantKindExternal,
			Plan:             tntent.TenantPlanOnDemand,
			SalesForceRegion: tntent.TenantRegionEMEA,
		}
	}

	It("applies fixed status, kind, and default country filters", func() {
		service := &svc{conf: config.UserBehaviorServiceConfig{
			TenantEventCapture: config.TenantEventCaptureConfig{Enabled: true},
		}}

		Expect(service.matchTenantEventCaptureTenant(eligibleTenant())).To(BeTrue())

		freeTrial := eligibleTenant()
		freeTrial.Plan = tntent.TenantPlanFreeTrial
		Expect(service.matchTenantEventCaptureTenant(freeTrial)).To(BeTrue())

		naTenant := eligibleTenant()
		naTenant.SalesForceRegion = tntent.TenantRegionNA
		Expect(service.matchTenantEventCaptureTenant(naTenant)).To(BeTrue())

		inactiveTenant := eligibleTenant()
		inactiveTenant.Status = tntent.TenantStatusInactive
		Expect(service.matchTenantEventCaptureTenant(inactiveTenant)).To(BeFalse())

		internalTenant := eligibleTenant()
		internalTenant.Kind = tntent.TenantKindInternal
		Expect(service.matchTenantEventCaptureTenant(internalTenant)).To(BeFalse())

		usTenant := eligibleTenant()
		usTenant.Country = "US"
		Expect(service.matchTenantEventCaptureTenant(usTenant)).To(BeFalse())
	})

	It("applies configured tenant filters", func() {
		service := &svc{conf: config.UserBehaviorServiceConfig{
			TenantEventCapture: config.TenantEventCaptureConfig{
				Enabled:       true,
				Regions:       []string{"EMEA"},
				ExcludedPlans: []string{"free_trial", "new_user"},
				Countries:     []string{"DE", "GB"},
			},
		}}

		Expect(service.matchTenantEventCaptureTenant(eligibleTenant())).To(BeTrue())

		freeTrial := eligibleTenant()
		freeTrial.Plan = tntent.TenantPlanFreeTrial
		Expect(service.matchTenantEventCaptureTenant(freeTrial)).To(BeFalse())

		naTenant := eligibleTenant()
		naTenant.SalesForceRegion = tntent.TenantRegionNA
		Expect(service.matchTenantEventCaptureTenant(naTenant)).To(BeFalse())

		usTenant := eligibleTenant()
		usTenant.Country = "US"
		Expect(service.matchTenantEventCaptureTenant(usTenant)).To(BeFalse())
	})

	It("falls back to country region when salesforce region is empty", func() {
		service := &svc{conf: config.UserBehaviorServiceConfig{
			TenantEventCapture: config.TenantEventCaptureConfig{
				Enabled: true,
				Regions: []string{"EMEA"},
			},
		}}

		tenant := eligibleTenant()
		tenant.SalesForceRegion = ""
		tenant.CountryRegion = tntent.TenantRegionEMEA
		Expect(service.matchTenantEventCaptureTenant(tenant)).To(BeTrue())

		tenant.CountryRegion = tntent.TenantRegionNA
		Expect(service.matchTenantEventCaptureTenant(tenant)).To(BeFalse())
	})

	It("does not enqueue excluded tenant IDs", func() {
		ch := make(chan *tenantEventCaptureEvent, 1)
		service := &svc{
			conf: config.UserBehaviorServiceConfig{
				TenantEventCapture: config.TenantEventCaptureConfig{
					Enabled:           true,
					ExcludedTenantIDs: []uint64{1372813089209300444},
				},
			},
			tenantEventCaptureCh: ch,
		}

		service.enqueueTenantEventCapture(&tenantEventCaptureEvent{
			EventUUID:   "uuid-excluded",
			EventSource: tenantEventCaptureSourceDisplay,
			EventType:   "CreateCluster",
			TenantID:    1372813089209300444,
		})
		Expect(ch).To(BeEmpty())

		service.enqueueTenantEventCapture(&tenantEventCaptureEvent{
			EventUUID:   "uuid-allowed",
			EventSource: tenantEventCaptureSourceDisplay,
			EventType:   "CreateCluster",
			TenantID:    123,
		})
		Expect(ch).To(HaveLen(1))
	})

	It("keeps only non-started display events by default", func() {
		service := &svc{conf: config.UserBehaviorServiceConfig{
			TenantEventCapture: config.TenantEventCaptureConfig{Enabled: true},
		}}

		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceDisplay,
			EventType:   DisplayEventCreateCluster,
			EventStatus: "started",
		})).To(BeFalse())
		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceDisplay,
			EventType:   DisplayEventCreateCluster,
			EventStatus: "succeeded",
		})).To(BeTrue())
		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceCluster,
			EventType:   "ClusterCreated",
		})).To(BeFalse())
	})

	It("allows capture event filters to be configured", func() {
		service := &svc{conf: config.UserBehaviorServiceConfig{
			TenantEventCapture: config.TenantEventCaptureConfig{
				Enabled: true,
				EventFilters: map[string]config.TenantEventCaptureEventFilter{
					tenantEventCaptureSourceCluster: {
						EventTypes: []string{"ClusterCreated"},
					},
				},
			},
		}}

		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceCluster,
			EventType:   "ClusterCreated",
		})).To(BeTrue())
		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceCluster,
			EventType:   "ClusterDeleted",
		})).To(BeFalse())
		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceDisplay,
			EventType:   DisplayEventCreateCluster,
		})).To(BeFalse())
	})

	It("applies event type and status filters per source", func() {
		service := &svc{conf: config.UserBehaviorServiceConfig{
			TenantEventCapture: config.TenantEventCaptureConfig{
				Enabled: true,
				EventFilters: map[string]config.TenantEventCaptureEventFilter{
					tenantEventCaptureSourceDisplay: {
						ExcludedStatuses: []string{"started"},
					},
					tenantEventCaptureSourceOrgEvent: {
						EventTypes: []string{"activate"},
					},
				},
			},
		}}

		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceDisplay,
			EventType:   DisplayEventCreateCluster,
			EventStatus: "started",
		})).To(BeFalse())
		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceDisplay,
			EventType:   DisplayEventCreateCluster,
			EventStatus: "succeeded",
		})).To(BeTrue())
		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceOrgEvent,
			EventType:   "activate",
			EventStatus: "started",
		})).To(BeTrue())
		Expect(service.matchTenantEventCaptureEvent(&tenantEventCaptureEvent{
			EventSource: tenantEventCaptureSourceOrgEvent,
			EventType:   "delete",
		})).To(BeFalse())
	})

	It("enables TLS for target DB by default", func() {
		dbConf := tenantEventCaptureDBConfig(config.TenantEventCaptureConfig{
			Enabled: true,
			TargetDB: &config.DatabaseConfig{
				Host:     "gateway01.us-west-2.prod.aws.tidbcloud.com",
				Port:     4000,
				User:     "root",
				Password: "password",
				Database: "regional_support",
			},
		})

		Expect(dbConf.TLSConfigKey).To(Equal("true"))
	})

	It("persists captured events for matching tenants", func() {
		ctrl := gomock.NewController(GinkgoT())
		tenantSvc := tenantsvc.NewMockTenantServiceAPI(ctrl)
		tenantSvc.EXPECT().GetTenant(gomock.Any(), uint64(123)).Return(eligibleTenant(), nil)

		db := newCaptureDB()
		service := &svc{
			conf: config.UserBehaviorServiceConfig{
				TenantEventCapture: config.TenantEventCaptureConfig{
					Enabled:       true,
					ExcludedPlans: []string{"new_user"},
				},
			},
			tenantSvc:            tenantSvc,
			tenantEventCaptureDB: db,
		}

		eventTime := time.Date(2026, 5, 21, 1, 2, 3, 0, time.UTC)
		clusterID := uint64(456)
		err := service.DispatchTenantEventCapture(context.Background(), &tenantEventCaptureEvent{
			EventUUID:   "uuid-1",
			EventSource: tenantEventCaptureSourceDisplay,
			EventType:   "CreateCluster",
			EventStatus: "started",
			TenantID:    123,
			ClusterID:   &clusterID,
			EventTime:   &eventTime,
			RawEvent:    map[string]any{"tenant_id": 123, "event": "CreateCluster"},
		})
		Expect(err).ToNot(HaveOccurred())

		var records []ubmod.TenantEventCapture
		Expect(db.Find(&records).Error).To(Succeed())
		Expect(records).To(HaveLen(1))
		Expect(records[0].TenantID).To(Equal(uint64(123)))
		Expect(records[0].Country).To(Equal("DE"))
		Expect(records[0].SalesForceRegion).To(Equal("EMEA"))
		Expect(records[0].RawEvent).To(ContainSubstring("CreateCluster"))
	})

	It("skips captured events for non-matching tenants", func() {
		ctrl := gomock.NewController(GinkgoT())
		tenantSvc := tenantsvc.NewMockTenantServiceAPI(ctrl)
		tenant := eligibleTenant()
		tenant.Plan = tntent.TenantPlanNewUser
		tenantSvc.EXPECT().GetTenant(gomock.Any(), uint64(123)).Return(tenant, nil)

		db := newCaptureDB()
		service := &svc{
			conf: config.UserBehaviorServiceConfig{
				TenantEventCapture: config.TenantEventCaptureConfig{
					Enabled:       true,
					ExcludedPlans: []string{"new_user"},
				},
			},
			tenantSvc:            tenantSvc,
			tenantEventCaptureDB: db,
		}

		err := service.DispatchTenantEventCapture(context.Background(), &tenantEventCaptureEvent{
			EventUUID:   "uuid-2",
			EventSource: tenantEventCaptureSourceOrgEvent,
			EventType:   "activate",
			TenantID:    123,
			RawEvent:    map[string]any{"tenant_id": 123, "event": "activate"},
		})
		Expect(err).ToNot(HaveOccurred())

		var count int64
		Expect(db.Model(&ubmod.TenantEventCapture{}).Count(&count).Error).To(Succeed())
		Expect(count).To(Equal(int64(0)))
	})
})
