package packs

import (
	"context"
	"database/sql"
	_ "embed"
	"fmt"
	"log"
	"strings"

	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-controlplane/layoutownership"
)

//go:embed user_page_layout.json
var userPageLayoutJSON []byte

const (
	curatedUserLayoutID      = "curated_User_default"
	curatedUserAssignmentID  = "curated_User_assignment_wildcard"
	curatedUserObjectAPIName = "User"
	curatedLayoutSource      = "curated"
)

// parseUserPageLayout decodes the embedded curated layout config and stamps
// the canonical identity fields (Id, ObjectAPIName, Source) so the JSON file
// cannot drift from the values the SQL upsert pins.
func parseUserPageLayout() (models.PageLayout, error) {
	layout, err := layoutownership.DecodePageLayoutConfig(userPageLayoutJSON)
	if err != nil {
		return layout, fmt.Errorf("parse user_page_layout.json: %w", err)
	}
	layout.ID = curatedUserLayoutID
	layout.ObjectAPIName = curatedUserObjectAPIName
	layout.Source = curatedLayoutSource
	layout.GenerationVersion = 0
	layout.IsCustomized = false
	layout.LayoutVersion = 0
	return layout, nil
}

func (p *PlatformUXPack) DeclaredComponents() ([]DeclaredComponent, error) {
	layout, err := parseUserPageLayout()
	if err != nil {
		return nil, err
	}
	payload, err := canonicalPageLayoutConfigJSON(layout)
	if err != nil {
		return nil, err
	}
	payloadHash := sha256Hex(payload)
	objectRef := ComponentReference{
		Kind:        ComponentKindObjectSpec,
		BusinessKey: layout.ObjectAPIName,
		Required:    true,
	}
	layoutKey := JoinComponentBusinessKey(layout.ObjectAPIName, layout.ID)
	components := []DeclaredComponent{
		{
			PackName:      p.Name(),
			Kind:          ComponentKindPageLayout,
			BusinessKey:   layoutKey,
			Required:      true,
			Manageability: ManageabilityLocked,
			PayloadHash:   payloadHash,
			References:    []ComponentReference{objectRef},
		},
		{
			PackName:      p.Name(),
			Kind:          ComponentKindLayoutAssignment,
			BusinessKey:   JoinComponentBusinessKey(layout.ObjectAPIName, curatedUserAssignmentID),
			Required:      true,
			Manageability: ManageabilityLocked,
			References: []ComponentReference{
				objectRef,
				{Kind: ComponentKindPageLayout, BusinessKey: layoutKey, Required: true},
			},
		},
	}

	themeComponents, err := declaredThemeComponents(p.Name())
	if err != nil {
		return nil, err
	}
	components = append(components, themeComponents...)

	uiComponents, err := declaredUIComponents(p.Name())
	if err != nil {
		return nil, err
	}
	components = append(components, uiComponents...)

	setupPageComponents, err := declaredSetupPageComponents(p.Name())
	if err != nil {
		return nil, err
	}
	components = append(components, setupPageComponents...)
	return components, nil
}

func declaredThemeComponents(packName string) ([]DeclaredComponent, error) {
	themes := parseThemes()
	components := make([]DeclaredComponent, 0, len(themes))
	for _, theme := range themes {
		payload, err := themeComponentPayload(theme)
		if err != nil {
			return nil, err
		}
		payloadHash, err := ComponentPayloadHash(payload)
		if err != nil {
			return nil, err
		}
		components = append(components, DeclaredComponent{
			PackName:      packName,
			Kind:          ComponentKindTheme,
			BusinessKey:   theme.Name,
			Required:      true,
			Manageability: ManageabilityLocked,
			PayloadHash:   payloadHash,
		})
	}
	return components, nil
}

func declaredUIComponents(packName string) ([]DeclaredComponent, error) {
	uiComponents := parseUIComponents()
	components := make([]DeclaredComponent, 0, len(uiComponents))
	for _, component := range uiComponents {
		payloadHash, err := ComponentPayloadHash(uiComponentPayload(component))
		if err != nil {
			return nil, err
		}
		components = append(components, DeclaredComponent{
			PackName:      packName,
			Kind:          ComponentKindUIComponent,
			BusinessKey:   component.Name,
			Required:      true,
			Manageability: ManageabilityLocked,
			PayloadHash:   payloadHash,
		})
	}
	return components, nil
}

func declaredSetupPageComponents(packName string) ([]DeclaredComponent, error) {
	setupPages := parseSetupPages()
	components := make([]DeclaredComponent, 0, len(setupPages))
	for _, page := range setupPages {
		payloadHash, err := ComponentPayloadHash(setupPagePayload(page))
		if err != nil {
			return nil, err
		}
		components = append(components, DeclaredComponent{
			PackName:      packName,
			Kind:          ComponentKindSetupPage,
			BusinessKey:   page.ComponentName,
			Required:      true,
			Manageability: ManageabilityLocked,
			PayloadHash:   payloadHash,
		})
	}
	return components, nil
}

func themeComponentPayload(theme models.Theme) (map[string]any, error) {
	return map[string]any{
		"name":     theme.Name,
		"isActive": theme.IsActive,
		"colors":   theme.Colors,
		"density":  theme.Density,
		"logoUrl":  stringPtrValue(theme.LogoURL),
	}, nil
}

func uiComponentPayload(component models.UIComponent) map[string]any {
	if component.Description != nil && *component.Description == "" {
		component.Description = nil
	}
	return map[string]any{
		"name":          component.Name,
		"type":          component.Type,
		"isEmbeddable":  component.IsEmbeddable,
		"description":   stringPtrValue(component.Description),
		"componentPath": stringPtrValue(component.ComponentPath),
	}
}

func setupPagePayload(page models.SetupPage) map[string]any {
	return map[string]any{
		"id":                 page.ID,
		"label":              page.Label,
		"icon":               page.Icon,
		"componentName":      page.ComponentName,
		"category":           page.Category,
		"pageOrder":          page.PageOrder,
		"permissionRequired": page.PermissionRequired,
		"isEnabled":          page.IsEnabled,
		"description":        page.Description,
		"path":               page.Path,
	}
}

func stringPtrValue(value *string) string {
	if value == nil {
		return ""
	}
	return strings.TrimSpace(*value)
}

// applyUserPageLayout installs (or refreshes) the curated User page layout and
// the platform-owned wildcard default when that default is still L1-owned.
// Admin-owned rows/default assignments are preserved.
//
// Issue #656: without a curated layout, the auto-generator dumps every
// discovered physical User column (~165 fields including UserPreferences*)
// into the form. The curated layout below is the SF v63.0 admin
// Setup → New User page projected onto NexusCRM's domain — 33 fields across
// 6 sections. Source='curated' keeps the row out of the auto-regen path
// (metadata_layouts.go:142 only refreshes Source='auto_generated' rows).
//
// Idempotent: every restart converges to the same state for platform-owned
// rows while preserving admin-owned overrides.
func applyUserPageLayout(ctx context.Context, db *sql.DB) error {
	log.Println("🔧 Installing curated User page layout (issue #656)...")

	layout, err := parseUserPageLayout()
	if err != nil {
		return err
	}
	// The curated User layout is owned by the platform-ux pack. Keep admin/L2
	// authority encoded via RouteSource rather than by blanking OwnerPack.
	if err := applyCuratedPageLayout(ctx, db, layout, curatedUserAssignmentID, "User", (&PlatformUXPack{}).Name()); err != nil {
		return err
	}

	log.Printf("   ✅ Curated User layout installed (%d sections)", len(layout.Sections))
	return nil
}

func BackfillLegacyOwnerlessCuratedUserLayoutOwnership(ctx context.Context, db *sql.DB) error {
	ownerPack := (&PlatformUXPack{}).Name()
	if _, err := db.ExecContext(ctx, `
UPDATE _System_PageLayout
SET OwnerPack = ?, LastModifiedDate = CURRENT_TIMESTAMP
WHERE Id = ?
  AND LOWER(ObjectApiName) = LOWER(?)
  AND COALESCE(Source, '') = ?
  AND COALESCE(IsCustomized, 0) = 0
  AND COALESCE(OwnerPack, '') = ''`,
		ownerPack, curatedUserLayoutID, curatedUserObjectAPIName, curatedLayoutSource,
	); err != nil {
		return fmt.Errorf("claim legacy ownerless curated User layout row: %w", err)
	}
	if _, err := db.ExecContext(ctx, `
UPDATE _System_LayoutAssignment
SET OwnerPack = ?, LastModifiedDate = CURRENT_TIMESTAMP
WHERE Id = ?
  AND LOWER(ObjectApiName) = LOWER(?)
  AND LayoutId = ?
  AND RoleId IS NULL
  AND ProfileId IS NULL
  AND RecordTypeId IS NULL
  AND LOWER(COALESCE(RouteSource, '')) = LOWER(?)
  AND COALESCE(OwnerPack, '') = ''`,
		ownerPack, curatedUserAssignmentID, curatedUserObjectAPIName, curatedUserLayoutID, models.LayoutAssignmentRouteSourceManagedSeed,
	); err != nil {
		return fmt.Errorf("claim legacy ownerless curated User layout assignment: %w", err)
	}
	return nil
}

// verifyCuratedUserLayout is the pod-readiness gate: confirms the layout row
// exists, is not soft-deleted, and is pointed at by the wildcard assignment.
func verifyCuratedUserLayout(db *sql.DB) error {
	layout, err := parseUserPageLayout()
	if err != nil {
		return err
	}
	if err := verifyCuratedPageLayout(db, layout, (&PlatformUXPack{}).Name(), "User"); err != nil {
		return err
	}
	return verifyCuratedPageLayoutConfigForPack(
		context.Background(),
		db,
		layout,
		layout.RelatedLists,
		nil,
		(&PlatformUXPack{}).Name(),
		"User",
	)
}
