package packs

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
)

type ComponentSurfaceRole string

const (
	ComponentSurfaceRoleComponentRow        ComponentSurfaceRole = "component_row"
	ComponentSurfaceRoleReferenceOnly       ComponentSurfaceRole = "reference_only"
	ComponentSurfaceRoleRuntimeDataExcluded ComponentSurfaceRole = "runtime_data_excluded"
)

type ComponentOwnerStrategy string

const (
	ComponentOwnerStrategyOwnerlessManifest   ComponentOwnerStrategy = "ownerless_manifest"
	ComponentOwnerStrategyOwnerPack           ComponentOwnerStrategy = "owner_pack"
	ComponentOwnerStrategySource              ComponentOwnerStrategy = "source"
	ComponentOwnerStrategyRuntimeDataExcluded ComponentOwnerStrategy = "runtime_data_excluded"
)

type ComponentSurface struct {
	Name          string
	Table         string
	Role          ComponentSurfaceRole
	Kind          ComponentKind
	OwnerStrategy ComponentOwnerStrategy
	LoadLiveRows  func(context.Context, *sql.DB) ([]LiveComponent, error)
}

type LiveComponent struct {
	Kind        ComponentKind
	BusinessKey string
	PayloadHash string
	Sample      string
}

func ComponentSurfaceRegistry() []ComponentSurface {
	return OwnerlessRuntimeComponentSurfaces()
}

func OwnerlessRuntimeComponentSurfaces() []ComponentSurface {
	return []ComponentSurface{
		ownerlessRuntimeComponentSurface(constants.TableTheme, ComponentKindTheme, loadThemeComponents),
		ownerlessRuntimeComponentSurface(constants.TableUIComponent, ComponentKindUIComponent, loadUIComponents),
		ownerlessRuntimeComponentSurface(constants.TableSetupPage, ComponentKindSetupPage, loadSetupPageComponents),
		ownerlessRuntimeComponentSurface(constants.TableAppMenuItem, ComponentKindAppMenuItem, loadAppMenuItemComponents),
		ownerlessRuntimeComponentSurface(constants.TableReportFolder, ComponentKindReportFolder, loadReportFolderComponents),
		ownerlessRuntimeComponentSurface(constants.TableFolderShare, ComponentKindFolderShare, loadFolderShareComponents),
	}
}

func ownerlessRuntimeComponentSurface(table string, kind ComponentKind, load func(context.Context, *sql.DB) ([]LiveComponent, error)) ComponentSurface {
	return ComponentSurface{
		Name:          table,
		Table:         table,
		Role:          ComponentSurfaceRoleComponentRow,
		Kind:          kind,
		OwnerStrategy: ComponentOwnerStrategyOwnerlessManifest,
		LoadLiveRows:  load,
	}
}

func VerifyDeclaredComponentRows(ctx context.Context, db *sql.DB, packName string, declared []DeclaredComponent, surfaces []ComponentSurface, kinds ...ComponentKind) error {
	selectedKinds := map[ComponentKind]struct{}{}
	for _, kind := range kinds {
		selectedKinds[kind] = struct{}{}
	}
	includeKind := func(kind ComponentKind) bool {
		if len(selectedKinds) == 0 {
			return true
		}
		_, ok := selectedKinds[kind]
		return ok
	}

	liveByKind := map[ComponentKind]map[string][]LiveComponent{}
	for _, surface := range surfaces {
		if surface.LoadLiveRows == nil || !includeKind(surface.Kind) {
			continue
		}
		live, err := surface.LoadLiveRows(ctx, db)
		if err != nil {
			return err
		}
		byKey := liveByKind[surface.Kind]
		if byKey == nil {
			byKey = map[string][]LiveComponent{}
			liveByKind[surface.Kind] = byKey
		}
		for _, row := range live {
			byKey[row.BusinessKey] = append(byKey[row.BusinessKey], row)
		}
	}

	var errs []string
	for _, want := range declared {
		if packName != "" && want.PackName != packName {
			continue
		}
		if !includeKind(want.Kind) {
			continue
		}
		byKey, ok := liveByKind[want.Kind]
		if !ok {
			errs = append(errs, fmt.Sprintf("%s %s has no registered live-row surface", want.Kind, want.BusinessKey))
			continue
		}
		rows := byKey[want.BusinessKey]
		if len(rows) == 0 {
			if want.Required {
				errs = append(errs, fmt.Sprintf("%s %s is missing", want.Kind, want.BusinessKey))
			}
			continue
		}
		if len(rows) > 1 {
			samples := make([]string, 0, len(rows))
			for _, row := range rows {
				samples = append(samples, row.Sample)
			}
			errs = append(errs, fmt.Sprintf("%s %s has duplicate live business keys: %s", want.Kind, want.BusinessKey, strings.Join(samples, ", ")))
			continue
		}
		if want.PayloadHash != "" && rows[0].PayloadHash != "" && rows[0].PayloadHash != want.PayloadHash {
			errs = append(errs, fmt.Sprintf("%s %s payload drifted", want.Kind, want.BusinessKey))
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("declared component rows drift:\n  - %s", strings.Join(errs, "\n  - "))
	}
	return nil
}

func loadThemeComponents(ctx context.Context, db *sql.DB) ([]LiveComponent, error) {
	rows, err := db.QueryContext(ctx, `
		SELECT Name, COALESCE(IsActive, 0), COALESCE(Colors, '{}'), COALESCE(Density, ''), COALESCE(LogoUrl, '')
		FROM _System_Theme`)
	if err != nil {
		return nil, fmt.Errorf("query _System_Theme manifest components: %w", err)
	}
	defer rows.Close()

	var out []LiveComponent
	for rows.Next() {
		var name, colorsJSON, density, logoURL string
		var isActive int
		if err := rows.Scan(&name, &isActive, &colorsJSON, &density, &logoURL); err != nil {
			return nil, fmt.Errorf("scan _System_Theme manifest component: %w", err)
		}
		var colors any
		if err := json.Unmarshal([]byte(colorsJSON), &colors); err != nil {
			return nil, fmt.Errorf("parse _System_Theme %s colors: %w", name, err)
		}
		hash, err := ComponentPayloadHash(map[string]any{
			"name":     name,
			"isActive": isActive != 0,
			"colors":   colors,
			"density":  density,
			"logoUrl":  logoURL,
		})
		if err != nil {
			return nil, fmt.Errorf("hash _System_Theme %s payload: %w", name, err)
		}
		out = append(out, LiveComponent{
			Kind:        ComponentKindTheme,
			BusinessKey: name,
			PayloadHash: hash,
			Sample:      name,
		})
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate _System_Theme manifest components: %w", err)
	}
	return out, nil
}

func loadUIComponents(ctx context.Context, db *sql.DB) ([]LiveComponent, error) {
	rows, err := db.QueryContext(ctx, `
		SELECT Name, COALESCE(Type, ''), COALESCE(IsEmbeddable, 0), COALESCE(Description, ''), COALESCE(ComponentPath, '')
		FROM _System_UIComponent`)
	if err != nil {
		return nil, fmt.Errorf("query _System_UIComponent manifest components: %w", err)
	}
	defer rows.Close()

	var out []LiveComponent
	for rows.Next() {
		var name, componentType, description, componentPath string
		var isEmbeddable int
		if err := rows.Scan(&name, &componentType, &isEmbeddable, &description, &componentPath); err != nil {
			return nil, fmt.Errorf("scan _System_UIComponent manifest component: %w", err)
		}
		hash, err := ComponentPayloadHash(map[string]any{
			"name":          name,
			"type":          componentType,
			"isEmbeddable":  isEmbeddable != 0,
			"description":   strings.TrimSpace(description),
			"componentPath": strings.TrimSpace(componentPath),
		})
		if err != nil {
			return nil, fmt.Errorf("hash _System_UIComponent %s payload: %w", name, err)
		}
		out = append(out, LiveComponent{
			Kind:        ComponentKindUIComponent,
			BusinessKey: name,
			PayloadHash: hash,
			Sample:      name,
		})
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate _System_UIComponent manifest components: %w", err)
	}
	return out, nil
}

func loadSetupPageComponents(ctx context.Context, db *sql.DB) ([]LiveComponent, error) {
	rows, err := db.QueryContext(ctx, `
		SELECT Id, Label, COALESCE(Icon, ''), ComponentName, Category, COALESCE(PageOrder, 0),
		       COALESCE(PermissionRequired, ''), COALESCE(IsEnabled, 0), COALESCE(Description, ''), COALESCE(Path, '')
		FROM _System_SetupPage`)
	if err != nil {
		return nil, fmt.Errorf("query _System_SetupPage manifest components: %w", err)
	}
	defer rows.Close()

	var out []LiveComponent
	for rows.Next() {
		var id, label, icon, componentName, category, permissionRequired, description, path string
		var pageOrder, isEnabled int
		if err := rows.Scan(&id, &label, &icon, &componentName, &category, &pageOrder, &permissionRequired, &isEnabled, &description, &path); err != nil {
			return nil, fmt.Errorf("scan _System_SetupPage manifest component: %w", err)
		}
		hash, err := ComponentPayloadHash(map[string]any{
			"id":                 id,
			"label":              label,
			"icon":               strings.TrimSpace(icon),
			"componentName":      componentName,
			"category":           category,
			"pageOrder":          pageOrder,
			"permissionRequired": strings.TrimSpace(permissionRequired),
			"isEnabled":          isEnabled != 0,
			"description":        strings.TrimSpace(description),
			"path":               strings.TrimSpace(path),
		})
		if err != nil {
			return nil, fmt.Errorf("hash _System_SetupPage %s payload: %w", componentName, err)
		}
		out = append(out, LiveComponent{
			Kind:        ComponentKindSetupPage,
			BusinessKey: componentName,
			PayloadHash: hash,
			Sample:      componentName,
		})
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate _System_SetupPage manifest components: %w", err)
	}
	return out, nil
}

func loadAppMenuItemComponents(ctx context.Context, db *sql.DB) ([]LiveComponent, error) {
	rows, err := db.QueryContext(ctx, `
		SELECT Id, COALESCE(Label, ''), COALESCE(Description, ''), COALESCE(StartUrl, ''), COALESCE(Type, ''),
		       COALESCE(IsVisible, 0), COALESCE(SortOrder, 0), COALESCE(AppId, ''), COALESCE(Icon, ''), COALESCE(Color, '')
		FROM AppMenuItem
		WHERE COALESCE(IsDeleted, 0) = 0`)
	if err != nil {
		return nil, fmt.Errorf("query AppMenuItem manifest components: %w", err)
	}
	defer rows.Close()

	var out []LiveComponent
	for rows.Next() {
		var id, label, description, startURL, itemType, appID, icon, color string
		var isVisible, sortOrder int
		if err := rows.Scan(&id, &label, &description, &startURL, &itemType, &isVisible, &sortOrder, &appID, &icon, &color); err != nil {
			return nil, fmt.Errorf("scan AppMenuItem manifest component: %w", err)
		}
		businessKey := appMenuItemBusinessKey(itemType, appID, startURL)
		hash, err := ComponentPayloadHash(map[string]any{
			"label":       strings.TrimSpace(label),
			"description": strings.TrimSpace(description),
			"startUrl":    strings.TrimSpace(startURL),
			"type":        strings.TrimSpace(itemType),
			"isVisible":   isVisible != 0,
			"sortOrder":   sortOrder,
			"appId":       strings.TrimSpace(appID),
			"icon":        strings.TrimSpace(icon),
			"color":       strings.TrimSpace(color),
		})
		if err != nil {
			return nil, fmt.Errorf("hash AppMenuItem %s payload: %w", id, err)
		}
		out = append(out, LiveComponent{
			Kind:        ComponentKindAppMenuItem,
			BusinessKey: businessKey,
			PayloadHash: hash,
			Sample:      fmt.Sprintf("%s/%s/%s", id, strings.TrimSpace(appID), strings.TrimSpace(startURL)),
		})
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate AppMenuItem manifest components: %w", err)
	}
	return out, nil
}

func appMenuItemBusinessKey(itemType, appID, startURL string) string {
	return JoinComponentBusinessKey(strings.TrimSpace(itemType), strings.TrimSpace(appID), strings.TrimSpace(startURL))
}

func loadReportFolderComponents(ctx context.Context, db *sql.DB) ([]LiveComponent, error) {
	rows, err := db.QueryContext(ctx, `
		SELECT Id, Name, DeveloperName, Type, AccessType
		FROM ReportFolder
		WHERE COALESCE(IsDeleted, 0) = 0`)
	if err != nil {
		return nil, fmt.Errorf("query ReportFolder manifest components: %w", err)
	}
	defer rows.Close()

	var out []LiveComponent
	for rows.Next() {
		var id, name, developerName, folderType, accessType string
		if err := rows.Scan(&id, &name, &developerName, &folderType, &accessType); err != nil {
			return nil, fmt.Errorf("scan ReportFolder manifest component: %w", err)
		}
		hash, err := ComponentPayloadHash(map[string]any{
			"id":            id,
			"name":          name,
			"developerName": developerName,
			"type":          folderType,
			"accessType":    accessType,
		})
		if err != nil {
			return nil, fmt.Errorf("hash ReportFolder %s payload: %w", id, err)
		}
		out = append(out, LiveComponent{
			Kind:        ComponentKindReportFolder,
			BusinessKey: id,
			PayloadHash: hash,
			Sample:      fmt.Sprintf("%s/%s", id, developerName),
		})
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate ReportFolder manifest components: %w", err)
	}
	return out, nil
}

func loadFolderShareComponents(ctx context.Context, db *sql.DB) ([]LiveComponent, error) {
	rows, err := db.QueryContext(ctx, `
		SELECT Id, FolderId, SharedToId, SharedToType, AccessLevel, COALESCE(CreatedById, '')
		FROM FolderShare`)
	if err != nil {
		return nil, fmt.Errorf("query FolderShare manifest components: %w", err)
	}
	defer rows.Close()

	var out []LiveComponent
	for rows.Next() {
		var id, folderID, sharedToID, sharedToType, accessLevel, createdByID string
		if err := rows.Scan(&id, &folderID, &sharedToID, &sharedToType, &accessLevel, &createdByID); err != nil {
			return nil, fmt.Errorf("scan FolderShare manifest component: %w", err)
		}
		hash, err := ComponentPayloadHash(map[string]any{
			"id":           id,
			"folderId":     folderID,
			"sharedToId":   sharedToID,
			"sharedToType": sharedToType,
			"accessLevel":  accessLevel,
			"createdById":  strings.TrimSpace(createdByID),
		})
		if err != nil {
			return nil, fmt.Errorf("hash FolderShare %s payload: %w", id, err)
		}
		out = append(out, LiveComponent{
			Kind:        ComponentKindFolderShare,
			BusinessKey: id,
			PayloadHash: hash,
			Sample:      fmt.Sprintf("%s/%s", folderID, sharedToType),
		})
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate FolderShare manifest components: %w", err)
	}
	return out, nil
}
