package handlers

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"log"

	"switchboard-core/models"
	"switchboard-core/store"
	"switchboard-core/triggers"
)

// manifestTrigger is the manifest.json trigger declaration shape.
type manifestTrigger struct {
	Type       string `json:"type"`        // "event" or "webhook"
	Pattern    string `json:"pattern"`     // event pattern (for type=event)
	Slug       string `json:"slug"`        // webhook slug (for type=webhook)
	EntryPoint string `json:"entry_point"` // Starlark function name
}

// SyncManifestTriggers parses the "triggers" array from a package manifest
// and syncs them with the trigger store. Removes triggers no longer in the
// manifest (declarative sync). For webhooks, generates an HMAC secret on
// first create.
//
// Called from admin install and package install.
func SyncManifestTriggers(ctx context.Context, stores store.Stores, engine *triggers.Engine, pkgID string, manifest map[string]any) {
	if stores.Triggers == nil {
		return
	}

	// Parse triggers from manifest
	triggersRaw, ok := manifest["triggers"]
	if !ok {
		return
	}

	data, err := json.Marshal(triggersRaw)
	if err != nil {
		return
	}

	var declared []manifestTrigger
	if err := json.Unmarshal(data, &declared); err != nil {
		log.Printf("  ⚠️  trigger-sync[%s]: invalid triggers in manifest: %v", pkgID, err)
		return
	}

	if len(declared) == 0 {
		return
	}

	// Get existing triggers for this package
	existing, err := stores.Triggers.ListByPackage(ctx, pkgID)
	if err != nil {
		log.Printf("  ⚠️  trigger-sync[%s]: failed to list existing: %v", pkgID, err)
		return
	}

	// Build lookup maps
	existingByKey := make(map[string]*models.Trigger, len(existing))
	for i := range existing {
		key := triggerKey(&existing[i])
		existingByKey[key] = &existing[i]
	}

	declaredKeys := make(map[string]bool, len(declared))
	created, updated := 0, 0

	for _, dt := range declared {
		if dt.Type != models.TriggerTypeEvent && dt.Type != models.TriggerTypeWebhook {
			log.Printf("  ⚠️  trigger-sync[%s]: unknown trigger type %q, skipping", pkgID, dt.Type)
			continue
		}
		if dt.EntryPoint == "" {
			continue
		}

		key := dt.Type + ":" + triggerIdentifier(dt)
		declaredKeys[key] = true

		if ex, ok := existingByKey[key]; ok {
			// Update if entry_point changed
			if ex.EntryPoint != dt.EntryPoint {
				ex.EntryPoint = dt.EntryPoint
				if err := stores.Triggers.Update(ctx, ex); err != nil {
					log.Printf("  ⚠️  trigger-sync[%s]: update failed: %v", pkgID, err)
				} else {
					updated++
				}
			}
			continue
		}

		// Create new trigger
		t := &models.Trigger{
			PackageID:  pkgID,
			Type:       dt.Type,
			Enabled:    true,
			EntryPoint: dt.EntryPoint,
			Config:     json.RawMessage("{}"),
		}

		switch dt.Type {
		case models.TriggerTypeEvent:
			t.EventPattern = dt.Pattern
		case models.TriggerTypeWebhook:
			t.Slug = dt.Slug
			t.Secret = generateSecret()
		}

		if err := stores.Triggers.Create(ctx, t); err != nil {
			log.Printf("  ⚠️  trigger-sync[%s]: create failed: %v", pkgID, err)
			continue
		}
		created++

		// Register with engine
		if engine != nil {
			// Re-fetch to get the generated ID
			switch dt.Type {
			case models.TriggerTypeWebhook:
				if wh, _ := stores.Triggers.GetWebhook(ctx, pkgID, dt.Slug); wh != nil {
					engine.RegisterTrigger(wh)
				}
			case models.TriggerTypeEvent:
				// Need to fetch by package to find the new one
				triggers, _ := stores.Triggers.ListByPackage(ctx, pkgID)
				for i := range triggers {
					if triggers[i].EventPattern == dt.Pattern {
						engine.RegisterTrigger(&triggers[i])
						break
					}
				}
			}
		}
	}

	// Remove triggers no longer in manifest
	removed := 0
	for key, ex := range existingByKey {
		if !declaredKeys[key] {
			if engine != nil {
				engine.UnregisterTrigger(ex.ID)
			}
			if err := stores.Triggers.Delete(ctx, ex.ID); err != nil {
				log.Printf("  ⚠️  trigger-sync[%s]: delete failed: %v", pkgID, err)
			} else {
				removed++
			}
		}
	}

	if created+updated+removed > 0 {
		log.Printf("  🔔 trigger-sync[%s]: %d created, %d updated, %d removed",
			pkgID, created, updated, removed)
	}
}

// triggerKey returns a unique key for a trigger within a package.
func triggerKey(t *models.Trigger) string {
	return t.Type + ":" + triggerIdentifierFromModel(t)
}

func triggerIdentifier(dt manifestTrigger) string {
	switch dt.Type {
	case models.TriggerTypeEvent:
		return dt.Pattern
	case models.TriggerTypeWebhook:
		return dt.Slug
	}
	return ""
}

func triggerIdentifierFromModel(t *models.Trigger) string {
	switch t.Type {
	case models.TriggerTypeEvent:
		return t.EventPattern
	case models.TriggerTypeWebhook:
		return t.Slug
	}
	return ""
}

// generateSecret creates a random 32-byte hex-encoded secret for webhook HMAC.
func generateSecret() string {
	b := make([]byte, 32)
	_, _ = rand.Read(b)
	return hex.EncodeToString(b)
}
