package userbehavior

import (
	"context"
	"encoding/json"
	"fmt"
	"slices"
	"strings"
	"time"

	"github.com/tidbcloud/devops/entity/event"
	tntent "github.com/tidbcloud/devops/entity/tenant"
	ubmod "github.com/tidbcloud/devops/models/userbehavior"
	"github.com/tidbcloud/devops/pkg/infra/config"
	evbus "github.com/tidbcloud/eventbus-datastruct/event"
	mgmtevent "github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const (
	tenantEventCaptureSourceTenantPlan = "tenant_plan"
	tenantEventCaptureSourceOrgEvent   = "org_event"
	tenantEventCaptureSourceCluster    = "cluster_event"
	tenantEventCaptureSourceDisplay    = "display_event"
)

var defaultTenantEventCaptureCountries = []string{
	"GB", "DE", "FR", "IT", "NL", "BE", "CH", "AT", "IE", "LU", "MC", "AD",
	"SE", "DK", "FI", "NO", "IS", "AX", "FO",
	"ES", "PT", "GR", "MT", "CY", "GI", "VA", "SM",
	"PL", "CZ", "HU", "RO", "SK", "SI", "HR", "BG", "EE", "LV", "LT", "RS", "BA", "ME", "MK", "AL", "TR",
	"IL", "IN",
}

var (
	defaultTenantEventCaptureEventFilters = map[string]config.TenantEventCaptureEventFilter{
		tenantEventCaptureSourceDisplay: {
			ExcludedStatuses: []string{string(evbus.EventStatusStarted)},
		},
	}
)

type tenantEventCaptureEvent struct {
	EventUUID   string
	EventSource string
	EventType   string
	EventStatus string
	TenantID    uint64
	ClusterID   *uint64
	EventTime   *time.Time
	RawEvent    any
}

func newTenantPlanCaptureEvent(uuid string, ev *event.TenantPlanEvent) *tenantEventCaptureEvent {
	if ev == nil {
		return nil
	}
	return &tenantEventCaptureEvent{
		EventUUID:   uuid,
		EventSource: tenantEventCaptureSourceTenantPlan,
		EventType:   "plan_change",
		EventStatus: ev.ChangedStatus,
		TenantID:    ev.TenantID,
		RawEvent:    ev,
	}
}

func newOrgCaptureEvent(uuid string, ev *event.TenantEvent) *tenantEventCaptureEvent {
	if ev == nil {
		return nil
	}
	return &tenantEventCaptureEvent{
		EventUUID:   uuid,
		EventSource: tenantEventCaptureSourceOrgEvent,
		EventType:   string(ev.OrgEventType),
		TenantID:    ev.OrgId,
		EventTime:   ev.EventTime,
		RawEvent:    ev,
	}
}

func newClusterCaptureEvent(uuid string, ev *mgmtevent.ClusterEvent) *tenantEventCaptureEvent {
	if ev == nil {
		return nil
	}
	var eventTime *time.Time
	if ev.EventCreatedAtMs > 0 {
		t := time.UnixMilli(ev.EventCreatedAtMs).UTC()
		eventTime = &t
	}
	return &tenantEventCaptureEvent{
		EventUUID:   uuid,
		EventSource: tenantEventCaptureSourceCluster,
		EventType:   string(ev.EventType),
		TenantID:    ev.TenantID,
		ClusterID:   &ev.ClusterID,
		EventTime:   eventTime,
		RawEvent:    ev,
	}
}

func newDisplayCaptureEvent(uuid string, ev *evbus.DisplayEvent) *tenantEventCaptureEvent {
	if ev == nil {
		return nil
	}
	return &tenantEventCaptureEvent{
		EventUUID:   uuid,
		EventSource: tenantEventCaptureSourceDisplay,
		EventType:   ev.DisplayEventType,
		EventStatus: string(ev.DisplayStatus),
		TenantID:    ev.TenantID,
		ClusterID:   &ev.ClusterID,
		RawEvent:    ev,
	}
}

func (s *svc) enqueueTenantEventCapture(ev *tenantEventCaptureEvent) {
	if !s.conf.TenantEventCapture.Enabled || ev == nil || s.tenantEventCaptureCh == nil {
		return
	}
	if !s.matchTenantEventCaptureEvent(ev) {
		return
	}
	if slices.Contains(s.conf.TenantEventCapture.ExcludedTenantIDs, ev.TenantID) {
		return
	}
	s.tenantEventCaptureCh <- ev
}

func (s *svc) DispatchTenantEventCapture(ctx context.Context, ev *tenantEventCaptureEvent) error {
	if ev == nil || ev.TenantID == 0 || s.tenantEventCaptureDB == nil {
		return nil
	}

	tenant, err := s.tenantSvc.GetTenant(ctx, ev.TenantID)
	if err != nil {
		return fmt.Errorf("get tenant %d for tenant event capture failed: %w", ev.TenantID, err)
	}
	if tenant == nil || !s.matchTenantEventCaptureTenant(tenant) {
		return nil
	}

	rawEvent, err := json.Marshal(ev.RawEvent)
	if err != nil {
		return fmt.Errorf("marshal tenant event capture raw event failed: %w", err)
	}

	record := &ubmod.TenantEventCapture{
		EventUUID:        ev.EventUUID,
		EventSource:      ev.EventSource,
		EventType:        ev.EventType,
		EventStatus:      ev.EventStatus,
		TenantID:         tenant.Id,
		TenantName:       tenant.Name,
		TenantStatus:     string(tenant.Status),
		TenantKind:       string(tenant.Kind),
		TenantPlan:       string(tenant.Plan),
		SalesForceRegion: string(tenant.SalesForceRegion),
		Country:          strings.ToUpper(tenant.Country),
		ClusterID:        ev.ClusterID,
		EventTime:        ev.EventTime,
		RawEvent:         string(rawEvent),
	}

	err = s.tenantEventCaptureDB.WithContext(ctx).
		Clauses(clause.OnConflict{DoNothing: true}).
		Create(record).Error
	if err != nil {
		return fmt.Errorf("save tenant event capture failed: %w", err)
	}
	return nil
}

func (s *svc) matchTenantEventCaptureTenant(tenant *tntent.Tenant) bool {
	conf := s.conf.TenantEventCapture
	if tenant.Status != tntent.TenantStatusActive {
		return false
	}
	if tenant.Kind != tntent.TenantKindExternal {
		return false
	}
	if len(conf.Regions) > 0 && !containsFold(conf.Regions, tenantEventCaptureRegion(tenant)) {
		return false
	}
	if len(conf.ExcludedPlans) > 0 && containsFold(conf.ExcludedPlans, string(tenant.Plan)) {
		return false
	}
	countries := effectiveStringListNil(conf.Countries, defaultTenantEventCaptureCountries)
	return containsFold(countries, tenant.Country)
}

func (s *svc) matchTenantEventCaptureEvent(ev *tenantEventCaptureEvent) bool {
	conf := s.conf.TenantEventCapture
	filter, ok := tenantEventCaptureEventFilter(conf.EventFilters, ev.EventSource)
	if !ok {
		return false
	}

	if len(filter.EventTypes) > 0 && !containsFold(filter.EventTypes, ev.EventType) {
		return false
	}
	if ev.EventStatus != "" && containsFold(filter.ExcludedStatuses, ev.EventStatus) {
		return false
	}
	return true
}

func tenantEventCaptureRegion(tenant *tntent.Tenant) string {
	if strings.TrimSpace(string(tenant.SalesForceRegion)) != "" {
		return string(tenant.SalesForceRegion)
	}
	return string(tenant.CountryRegion)
}

func tenantEventCaptureEventFilter(filters map[string]config.TenantEventCaptureEventFilter, source string) (config.TenantEventCaptureEventFilter, bool) {
	if filters == nil {
		filters = defaultTenantEventCaptureEventFilters
	}
	for configuredSource, filter := range filters {
		if strings.EqualFold(strings.TrimSpace(configuredSource), strings.TrimSpace(source)) {
			return filter, true
		}
	}
	return config.TenantEventCaptureEventFilter{}, false
}

func effectiveStringListNil(values, defaults []string) []string {
	if values == nil {
		return defaults
	}
	return values
}

func containsFold(values []string, target string) bool {
	normalized := strings.ToUpper(strings.TrimSpace(target))
	return slices.ContainsFunc(values, func(v string) bool {
		return strings.ToUpper(strings.TrimSpace(v)) == normalized
	})
}

func validateTenantEventCaptureConfig(conf config.TenantEventCaptureConfig) error {
	if !conf.Enabled {
		return nil
	}
	if conf.TargetDB == nil {
		return fmt.Errorf("tenant event capture targetDB is required when enabled")
	}
	return nil
}

func tenantEventCaptureDBConfig(conf config.TenantEventCaptureConfig) config.DatabaseConfig {
	dbConf := *conf.TargetDB
	if strings.TrimSpace(dbConf.TLSConfigKey) == "" {
		dbConf.TLSConfigKey = "true"
	}
	return dbConf
}

func migrateTenantEventCaptureTable(db *gorm.DB) error {
	if db.Migrator().HasTable(&ubmod.TenantEventCapture{}) {
		return nil
	}
	return db.AutoMigrate(&ubmod.TenantEventCapture{})
}
