package packs

import (
	"bytes"
	"context"
	_ "embed"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"strings"
	"time"

	"github.com/nexuscrm/automation/triggerprojection"
	"github.com/nexuscrm/metadata"
	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
	zlog "github.com/rs/zerolog/log"
)

//go:embed themes.json
var themesJSON []byte

//go:embed ui_components.json
var uiComponentsJSON []byte

//go:embed setup_pages.json
var setupPagesJSON []byte

// ---- Types ----

type themeSeedData struct {
	Themes []themeSeed `json:"themes"`
}

type themeSeed struct {
	Name     string                 `json:"name"`
	IsActive *bool                  `json:"is_active"`
	Colors   map[string]interface{} `json:"colors"`
	Density  string                 `json:"density"`
	LogoURL  *string                `json:"logo_url"`
}

type uiComponentSeedData struct {
	Components []uiComponentSeed `json:"components"`
}

type uiComponentSeed struct {
	Name          string  `json:"name"`
	Type          string  `json:"type"`
	IsEmbeddable  *bool   `json:"is_embeddable"`
	Description   *string `json:"description"`
	ComponentPath *string `json:"component_path,omitempty"`
}

// ---- Pack ----

// PlatformUXPack manages platform-owned UX/UI metadata: themes, UI components,
// setup pages, and generic flow cache maintenance.
type PlatformUXPack struct{}

func (p *PlatformUXPack) Name() string       { return "platform-ux" }
func (p *PlatformUXPack) Category() Category { return PlatformUX }

func (p *PlatformUXPack) Apply(ctx context.Context, deps ApplyDeps) error {
	log.Println("📦 Applying platform-ux pack...")

	// Curated User page layout (issue #656).
	if err := applyUserPageLayout(ctx, deps.DB); err != nil {
		return fmt.Errorf("user page layout: %w", err)
	}

	// Themes
	if err := applyThemes(ctx, deps.Metadata); err != nil {
		return fmt.Errorf("themes: %w", err)
	}

	// UI components
	if err := applyUIComponents(ctx, deps.Metadata); err != nil {
		return fmt.Errorf("UI components: %w", err)
	}

	// Setup pages
	if err := applySetupPages(ctx, deps.Metadata); err != nil {
		return fmt.Errorf("setup pages: %w", err)
	}

	// Flows
	if err := applyFlows(ctx, deps.Metadata); err != nil {
		return fmt.Errorf("flows: %w", err)
	}

	log.Println("✅ platform-ux pack applied")
	return nil
}

func (p *PlatformUXPack) Verify(ctx context.Context, deps VerifyDeps) error {
	var errs []string

	declared, err := DeclaredComponentsForPack(p)
	if err != nil {
		errs = append(errs, err.Error())
	} else if err := VerifyDeclaredComponentRows(ctx, deps.DB, p.Name(), declared, OwnerlessRuntimeComponentSurfaces(),
		ComponentKindTheme,
		ComponentKindUIComponent,
		ComponentKindSetupPage,
	); err != nil {
		errs = append(errs, err.Error())
	}

	if err := verifyCuratedUserLayout(deps.DB); err != nil {
		errs = append(errs, err.Error())
	}

	// Flows are excluded: they are user-created data (not platform-seeded),
	// and their backfill is best-effort — missing trigger info is not fatal.

	if len(errs) > 0 {
		return fmt.Errorf("platform-ux verify failed:\n  - %s", strings.Join(errs, "\n  - "))
	}
	return nil
}

func parseThemes() []models.Theme {
	themes, err := parseThemeSeeds(themesJSON)
	if err != nil {
		panic("corrupt embedded themes.json: " + err.Error())
	}
	return themeSeedsToModels(themes)
}

func parseUIComponents() []models.UIComponent {
	components, err := parseUIComponentSeeds(uiComponentsJSON)
	if err != nil {
		panic("corrupt embedded ui_components.json: " + err.Error())
	}
	return uiComponentSeedsToModels(components)
}

func parseThemeSeeds(raw []byte) ([]themeSeed, error) {
	var data themeSeedData
	if err := decodeStrictJSON(raw, &data); err != nil {
		return nil, err
	}
	for i, seed := range data.Themes {
		if strings.TrimSpace(seed.Name) == "" {
			return nil, fmt.Errorf("themes[%d] has empty name", i)
		}
		if seed.IsActive == nil {
			return nil, fmt.Errorf("themes[%d] %s missing required is_active", i, seed.Name)
		}
		if seed.Colors == nil {
			return nil, fmt.Errorf("themes[%d] %s missing required colors", i, seed.Name)
		}
		if strings.TrimSpace(seed.Density) == "" {
			return nil, fmt.Errorf("themes[%d] %s missing required density", i, seed.Name)
		}
		if seed.LogoURL == nil {
			return nil, fmt.Errorf("themes[%d] %s missing required logo_url", i, seed.Name)
		}
	}
	return data.Themes, nil
}

func themeSeedsToModels(seeds []themeSeed) []models.Theme {
	themes := make([]models.Theme, 0, len(seeds))
	for _, seed := range seeds {
		logoURL := strings.TrimSpace(*seed.LogoURL)
		themes = append(themes, models.Theme{
			Name:     strings.TrimSpace(seed.Name),
			IsActive: *seed.IsActive,
			Colors:   seed.Colors,
			Density:  strings.TrimSpace(seed.Density),
			LogoURL:  &logoURL,
		})
	}
	return themes
}

func parseUIComponentSeeds(raw []byte) ([]uiComponentSeed, error) {
	var data uiComponentSeedData
	if err := decodeStrictJSON(raw, &data); err != nil {
		return nil, err
	}
	for i, seed := range data.Components {
		if strings.TrimSpace(seed.Name) == "" {
			return nil, fmt.Errorf("components[%d] has empty name", i)
		}
		if strings.TrimSpace(seed.Type) == "" {
			return nil, fmt.Errorf("components[%d] %s missing required type", i, seed.Name)
		}
		if seed.IsEmbeddable == nil {
			return nil, fmt.Errorf("components[%d] %s missing required is_embeddable", i, seed.Name)
		}
	}
	return data.Components, nil
}

func uiComponentSeedsToModels(seeds []uiComponentSeed) []models.UIComponent {
	components := make([]models.UIComponent, 0, len(seeds))
	for _, seed := range seeds {
		var description *string
		if seed.Description != nil {
			trimmed := strings.TrimSpace(*seed.Description)
			if trimmed != "" {
				description = &trimmed
			}
		}
		var componentPath *string
		if seed.ComponentPath != nil {
			trimmed := strings.TrimSpace(*seed.ComponentPath)
			if trimmed != "" {
				componentPath = &trimmed
			}
		}
		components = append(components, models.UIComponent{
			Name:          strings.TrimSpace(seed.Name),
			Type:          strings.TrimSpace(seed.Type),
			IsEmbeddable:  *seed.IsEmbeddable,
			Description:   description,
			ComponentPath: componentPath,
		})
	}
	return components
}

func decodeStrictJSON(raw []byte, dest any) error {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(dest); err != nil {
		return err
	}
	if err := decoder.Decode(&struct{}{}); err != io.EOF {
		if err == nil {
			return fmt.Errorf("multiple JSON documents")
		}
		return err
	}
	return nil
}

func parseSetupPages() []models.SetupPage {
	var pages []models.SetupPage
	if err := json.Unmarshal(setupPagesJSON, &pages); err != nil {
		panic("corrupt embedded setup_pages.json: " + err.Error())
	}
	return pages
}

// ---- Implementation ----

func applyThemes(ctx context.Context, metaSvc *metadata.MetadataService) error {
	log.Println("🔧 Initializing themes...")
	seeds, err := parseThemeSeeds(themesJSON)
	if err != nil {
		return apperrors.NewInternalError("failed to parse themes.json", err)
	}
	for _, theme := range themeSeedsToModels(seeds) {
		if err := metaSvc.UpsertTheme(ctx, &theme); err != nil {
			log.Printf("   ⚠️  Failed to ensure theme %s: %v", theme.Name, err)
		} else {
			log.Printf("   ✅ %s theme ensured", theme.Name)
		}
	}
	return nil
}

func applyUIComponents(ctx context.Context, metaSvc *metadata.MetadataService) error {
	log.Println("🔧 Initializing UI components...")
	seeds, err := parseUIComponentSeeds(uiComponentsJSON)
	if err != nil {
		return apperrors.NewInternalError("failed to parse ui_components.json", err)
	}
	components := uiComponentSeedsToModels(seeds)
	for _, comp := range components {
		if comp.Description != nil && *comp.Description == "" {
			comp.Description = nil
		}
		if err := metaSvc.UpsertUIComponent(ctx, &comp); err != nil {
			log.Printf("   ⚠️  Failed to ensure component %s: %v", comp.Name, err)
		}
	}
	log.Printf("   ✅ Ensure %d UI components", len(components))
	return nil
}

func applySetupPages(ctx context.Context, metaSvc *metadata.MetadataService) error {
	log.Println("🔧 Initializing setup pages...")
	var pages []models.SetupPage
	if err := json.Unmarshal(setupPagesJSON, &pages); err != nil {
		return apperrors.NewInternalError("failed to parse setup_pages.json", err)
	}
	for _, page := range pages {
		if err := metaSvc.UpsertSetupPage(ctx, &page); err != nil {
			log.Printf("   ⚠️  Failed to ensure setup page %s: %v", page.Label, err)
		}
	}
	log.Printf("   ✅ Ensure %d setup pages", len(pages))
	return nil
}

func applyFlows(ctx context.Context, metaSvc *metadata.MetadataService) error {
	return ApplyFlowRuntimeMaintenance(ctx, metaSvc)
}

// ApplyFlowRuntimeMaintenance materializes runtime trigger metadata after a
// pack installs FlowDefinition/Flow rows. It is generic L0 automation
// maintenance: app packs provide flow metadata, and the platform derives the
// cached trigger lookup used by record-triggered dispatch.
func ApplyFlowRuntimeMaintenance(ctx context.Context, metaSvc *metadata.MetadataService) error {
	if metaSvc == nil {
		return fmt.Errorf("metadata service is required for flow runtime maintenance")
	}
	metaSvc.InvalidateCache()
	// Backfill trigger info
	backfillStart := time.Now()
	if err := backfillFlowTriggerInfo(ctx, metaSvc); err != nil {
		return err
	}
	zlog.Info().Dur("elapsed", time.Since(backfillStart)).Msg("   ⏱  backfillFlowTriggerInfo")

	shadowStart := time.Now()
	if err := metaSvc.BackfillTriggerSubscriptions(ctx); err != nil {
		return fmt.Errorf("backfill trigger subscriptions: %w", err)
	}
	zlog.Info().Dur("elapsed", time.Since(shadowStart)).Msg("   ⏱  backfillTriggerSubscriptions")

	// Refresh flow cache
	cacheStart := time.Now()
	flows := metaSvc.GetFlowDefinitions(ctx)
	zlog.Info().Dur("elapsed", time.Since(cacheStart)).Int("count", len(flows)).Msg("📋 Flow definitions loaded into cache")
	return nil
}

func backfillFlowTriggerInfo(ctx context.Context, metaSvc *metadata.MetadataService) error {
	loadStart := time.Now()
	flows := metaSvc.GetFlowDefinitions(ctx)
	zlog.Info().Dur("elapsed", time.Since(loadStart)).Int("count", len(flows)).
		Msg("   ⏱  backfill: GetFlowDefinitions")

	var needsProjectionSync []int
	for i, def := range flows {
		if def.ActiveVersionId != "" {
			needsProjectionSync = append(needsProjectionSync, i)
		}
	}
	zlog.Info().Int("total", len(flows)).Int("needsProjectionSync", len(needsProjectionSync)).
		Msg("   ⏱  backfill: flow scan complete")
	if len(needsProjectionSync) == 0 {
		return nil
	}

	versionStart := time.Now()
	versionIDs := make([]string, 0, len(needsProjectionSync))
	for _, idx := range needsProjectionSync {
		versionIDs = append(versionIDs, flows[idx].ActiveVersionId)
	}
	versions, err := metaSvc.GetFlowVersionsByIDs(ctx, versionIDs)
	if err != nil {
		return fmt.Errorf("load active flow versions for trigger projection: %w", err)
	}
	zlog.Info().Dur("elapsed", time.Since(versionStart)).Int("loaded", len(versions)).
		Msg("   ⏱  backfill: BulkGetFlowVersions")

	backfilled := 0
	for _, idx := range needsProjectionSync {
		def := flows[idx]
		version := versions[def.ActiveVersionId]
		if version == nil {
			continue
		}

		projection, err := triggerprojection.Resolve(def, version)
		if err != nil {
			return fmt.Errorf("resolve trigger projection for flow definition %s: %w", def.DeveloperName, err)
		}
		if projection.Kind == triggerprojection.KindUnknown {
			continue
		}
		if def.TriggerType == projection.TriggerType && def.TriggerObjectOrEvent == projection.ObjectOrEvent {
			continue
		}
		if err := metaSvc.BackfillTriggerInfo(ctx, def.Id, projection.TriggerType, projection.ObjectOrEvent); err != nil {
			return fmt.Errorf("write trigger projection for flow definition %s: %w", def.DeveloperName, err)
		}
		backfilled++
	}

	zlog.Info().Int("backfilled", backfilled).Int("needsProjectionSync", len(needsProjectionSync)).
		Msg("   ⏱  backfill: complete")
	return nil
}
