package tenantoverlay

import (
	"bytes"
	"context"
	"crypto/sha1"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/nexuscrm/backend/internal/controlplane/packs"
	apperrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
)

type tenantActionsData struct {
	FullActions                 []fullActionDef                    `json:"fullActions"`
	Overlays                    []actionOverlay                    `json:"overlays"`
	StandardActionFieldDefaults []tenantStandardActionFieldDefault `json:"standardActionFieldDefaults"`
}

type fullActionDef struct {
	Name          string                 `json:"name"`
	Label         string                 `json:"label"`
	Type          string                 `json:"type"`
	Icon          string                 `json:"icon"`
	ObjectAPIName string                 `json:"objectApiName,omitempty"`
	AppliesTo     []string               `json:"appliesTo,omitempty"`
	TargetObject  string                 `json:"targetObject,omitempty"`
	Config        map[string]interface{} `json:"config,omitempty"`
}

type actionOverlay struct {
	Name          string                 `json:"name"`
	ObjectAPIName string                 `json:"objectApiName"`
	ConfigOverlay map[string]interface{} `json:"configOverlay"`
}

type tenantStandardActionFieldDefault struct {
	StandardActionName string `json:"standardActionName"`
	TargetObject       string `json:"targetObject"`
	FieldAPIName       string `json:"fieldAPIName"`
	ValueFormula       string `json:"valueFormula"`
	Condition          string `json:"condition,omitempty"`
	Priority           int    `json:"priority,omitempty"`
}

// applyActionOverlays layers tenant action content on top of rows the
// platform packs have already written. _System_Action overlays patch UI/action
// rows, while standardActionFieldDefaults add org-owned standard-action default
// rows under _System_ActionFieldDefault.
func (p *Pack) applyActionOverlays(ctx context.Context, db *sql.DB) error {
	actionsJSON, ok, err := p.readOptional(ActionsFile)
	if err != nil {
		return err
	}
	if !ok {
		return nil
	}
	var tenant tenantActionsData
	if err := json.Unmarshal(actionsJSON, &tenant); err != nil {
		return apperrors.NewInternalError("failed to parse actions.json", err)
	}

	// Hygiene: reject sourceIdField/hydrateSource on the action-level
	// surface. Those keys were only meaningful to the deleted
	// workflow.childCopy executor; with that executor retired (Phase 2
	// single-runtime consolidation), they're universally inert, but
	// rejecting them at authoring time catches typos in seeds before
	// they become dead config. Object-level child cloning lives on
	// _System_Object.ChildCloneRules.
	for _, overlay := range tenant.Overlays {
		if err := packs.CheckStrictChildCopyInConfig(overlay.Name, overlay.ConfigOverlay); err != nil {
			return fmt.Errorf("tenant action overlay: %w", err)
		}
	}
	for _, action := range tenant.FullActions {
		if err := packs.CheckStrictChildCopyInConfig(action.Name, action.Config); err != nil {
			return fmt.Errorf("tenant fullAction: %w", err)
		}
	}

	for _, overlay := range tenant.Overlays {
		if overlay.Name == "" || overlay.ObjectAPIName == "" {
			return fmt.Errorf("tenant action overlay missing name/objectApiName")
		}
		if err := applySingleActionOverlay(ctx, db, overlay); err != nil {
			return err
		}
	}

	for _, action := range tenant.FullActions {
		if err := upsertFullAction(ctx, db, action); err != nil {
			return err
		}
	}
	for _, row := range tenant.StandardActionFieldDefaults {
		if err := applySingleStandardActionFieldDefault(ctx, db, row); err != nil {
			return err
		}
	}
	return nil
}

func applySingleActionOverlay(ctx context.Context, db *sql.DB, overlay actionOverlay) error {
	// Match by (ObjectApiName, Name) rather than by the predicted
	// `act_{obj}_{name}` Id. Older orgs (pre-StandardActionID cutover)
	// carry UUID Ids for standard actions; platform's INSERT ... ON
	// DUPLICATE KEY UPDATE silently UPDATEs those legacy rows via the
	// UNIQUE KEY on (ObjectApiName, Name) instead of inserting a new
	// deterministic-Id row, so a predicted-Id SELECT misses them and
	// would wrongly fail the overlay. See issue #1609 follow-up.
	selectSQL := fmt.Sprintf(
		"SELECT `%s`, `%s` FROM `%s` WHERE `%s` = ? AND `%s` = ? AND (`%s` = 0 OR `%s` IS NULL)",
		constants.ColID, constants.FieldSysAction_Config, constants.TableAction,
		constants.FieldSysAction_ObjectAPIName, constants.FieldSysAction_Name,
		constants.FieldIsDeleted, constants.FieldIsDeleted,
	)
	var rowID string
	var currentJSON sql.NullString
	err := db.QueryRowContext(ctx, selectSQL, overlay.ObjectAPIName, overlay.Name).
		Scan(&rowID, &currentJSON)
	if err == sql.ErrNoRows {
		return fmt.Errorf("overlay %q (%s): no platform action row; PlatformUX must apply first",
			overlay.Name, overlay.ObjectAPIName)
	}
	if err != nil {
		return fmt.Errorf("overlay %s.%s: select current config: %w", overlay.ObjectAPIName, overlay.Name, err)
	}

	currentCfg := map[string]interface{}{}
	if currentJSON.Valid && currentJSON.String != "" {
		if err := json.Unmarshal([]byte(currentJSON.String), &currentCfg); err != nil {
			return fmt.Errorf("overlay %s: parse current config: %w", rowID, err)
		}
	}
	mergedJSON, err := json.Marshal(deepMergeMap(currentCfg, overlay.ConfigOverlay))
	if err != nil {
		return fmt.Errorf("overlay %s: marshal merged config: %w", rowID, err)
	}
	if currentJSON.Valid && bytes.Equal(mergedJSON, []byte(currentJSON.String)) {
		return nil
	}

	updateSQL := fmt.Sprintf(
		"UPDATE `%s` SET `%s` = ?, `%s` = NOW() WHERE `%s` = ?",
		constants.TableAction, constants.FieldSysAction_Config,
		constants.FieldLastModifiedDate, constants.ColID,
	)
	if _, err := db.ExecContext(ctx, updateSQL, string(mergedJSON), rowID); err != nil {
		return fmt.Errorf("overlay %s: update: %w", rowID, err)
	}
	return nil
}

func upsertFullAction(ctx context.Context, db *sql.DB, a fullActionDef) error {
	if a.ObjectAPIName == "" {
		return fmt.Errorf("fullAction %q missing objectApiName", a.Name)
	}
	id := packs.StandardActionID(a.ObjectAPIName, a.Name)
	cfgJSON, err := json.Marshal(a.Config)
	if err != nil {
		return fmt.Errorf("fullAction %s: marshal config: %w", id, err)
	}
	var target interface{} = nil
	if a.TargetObject != "" {
		target = a.TargetObject
	}
	now := time.Now().Format("2006-01-02 15:04:05")

	upsertSQL := fmt.Sprintf(`
		INSERT INTO `+"`%s`"+` (`+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE
			`+"`%s`"+` = VALUES(`+"`%s`"+`),
			`+"`%s`"+` = VALUES(`+"`%s`"+`),
			`+"`%s`"+` = VALUES(`+"`%s`"+`),
			`+"`%s`"+` = VALUES(`+"`%s`"+`),
			`+"`%s`"+` = VALUES(`+"`%s`"+`),
			`+"`%s`"+` = VALUES(`+"`%s`"+`)`,
		constants.TableAction,
		constants.ColID,
		constants.FieldSysAction_ObjectAPIName,
		constants.FieldSysAction_Name,
		constants.FieldSysAction_Label,
		constants.FieldSysAction_Type,
		constants.FieldSysAction_Icon,
		constants.FieldSysAction_TargetObject,
		constants.FieldSysAction_Config,
		constants.FieldCreatedDate,
		constants.FieldLastModifiedDate,
		constants.FieldSysAction_Label, constants.FieldSysAction_Label,
		constants.FieldSysAction_Type, constants.FieldSysAction_Type,
		constants.FieldSysAction_Icon, constants.FieldSysAction_Icon,
		constants.FieldSysAction_TargetObject, constants.FieldSysAction_TargetObject,
		constants.FieldSysAction_Config, constants.FieldSysAction_Config,
		constants.FieldLastModifiedDate, constants.FieldLastModifiedDate,
	)
	if _, err := db.ExecContext(ctx, upsertSQL,
		id, a.ObjectAPIName, a.Name, a.Label, a.Type, a.Icon, target, string(cfgJSON), now, now); err != nil {
		return fmt.Errorf("fullAction %s: upsert: %w", id, err)
	}
	return nil
}

func applySingleStandardActionFieldDefault(ctx context.Context, db *sql.DB, row tenantStandardActionFieldDefault) error {
	if strings.TrimSpace(row.StandardActionName) == "" ||
		strings.TrimSpace(row.TargetObject) == "" ||
		strings.TrimSpace(row.FieldAPIName) == "" ||
		strings.TrimSpace(row.ValueFormula) == "" {
		return fmt.Errorf("tenant standardActionFieldDefault missing standardActionName/targetObject/fieldAPIName/valueFormula")
	}

	selectSQL := fmt.Sprintf(
		"SELECT `%s` FROM `%s` WHERE `%s` = ? AND (`%s` = 0 OR `%s` IS NULL) LIMIT 1",
		constants.FieldSysStandardAction_ID,
		constants.TableStandardAction,
		constants.FieldSysStandardAction_Name,
		constants.FieldSysStandardAction_IsDeleted, constants.FieldSysStandardAction_IsDeleted,
	)
	var actionID string
	if err := db.QueryRowContext(ctx, selectSQL, row.StandardActionName).Scan(&actionID); err != nil {
		if err == sql.ErrNoRows {
			return fmt.Errorf("tenant standardActionFieldDefault %q: no _System_StandardAction row; standard actions must apply first", row.StandardActionName)
		}
		return fmt.Errorf("tenant standardActionFieldDefault %q: lookup standard action: %w", row.StandardActionName, err)
	}

	now := time.Now().Format("2006-01-02 15:04:05")
	insertSQL := fmt.Sprintf(`
		INSERT INTO `+"`%s`"+` (`+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`, `+"`%s`"+`)
		VALUES (?, ?, ?, ?, ?, ?, ?, 1, 1, 0, ?, ?)
		ON DUPLICATE KEY UPDATE
			`+"`%s`"+` = VALUES(`+"`%s`"+`),
			`+"`%s`"+` = VALUES(`+"`%s`"+`),
			`+"`%s`"+` = 1,
			`+"`%s`"+` = 1,
			`+"`%s`"+` = 0,
			`+"`%s`"+` = VALUES(`+"`%s`"+`)`,
		constants.TableActionFieldDefault,
		constants.FieldSysActionFieldDefault_ID,
		constants.FieldSysActionFieldDefault_StandardActionID,
		constants.FieldSysActionFieldDefault_TargetObject,
		constants.FieldSysActionFieldDefault_FieldAPIName,
		constants.FieldSysActionFieldDefault_ValueFormula,
		constants.FieldSysActionFieldDefault_Condition,
		constants.FieldSysActionFieldDefault_Priority,
		constants.FieldSysActionFieldDefault_IsCustomized,
		constants.FieldSysActionFieldDefault_IsActive,
		constants.FieldSysActionFieldDefault_IsDeleted,
		constants.FieldCreatedDate,
		constants.FieldLastModifiedDate,
		constants.FieldSysActionFieldDefault_ValueFormula, constants.FieldSysActionFieldDefault_ValueFormula,
		constants.FieldSysActionFieldDefault_Condition, constants.FieldSysActionFieldDefault_Condition,
		constants.FieldSysActionFieldDefault_IsCustomized,
		constants.FieldSysActionFieldDefault_IsActive,
		constants.FieldSysActionFieldDefault_IsDeleted,
		constants.FieldLastModifiedDate, constants.FieldLastModifiedDate,
	)
	if _, err := db.ExecContext(ctx, insertSQL,
		tenantActionFieldDefaultID(actionID, row.TargetObject, row.FieldAPIName, row.Priority),
		actionID,
		row.TargetObject,
		row.FieldAPIName,
		row.ValueFormula,
		nullableTenantString(row.Condition),
		row.Priority,
		now,
		now,
	); err != nil {
		return fmt.Errorf("tenant standardActionFieldDefault %q/%s.%s: upsert: %w",
			row.StandardActionName, row.TargetObject, row.FieldAPIName, err)
	}
	return nil
}

func tenantActionFieldDefaultID(actionID, targetObject, fieldAPIName string, priority int) string {
	sum := sha1.Sum([]byte(strings.Join([]string{actionID, targetObject, fieldAPIName, fmt.Sprint(priority)}, "|")))
	return "tenant_afd_" + hex.EncodeToString(sum[:8])
}

func nullableTenantString(value string) interface{} {
	if strings.TrimSpace(value) == "" {
		return nil
	}
	return value
}

// deepMergeMap merges src into dst. Maps recurse; arrays / scalars /
// typed values are replaced wholesale by src when present.
func deepMergeMap(dst, src map[string]interface{}) map[string]interface{} {
	if dst == nil {
		dst = map[string]interface{}{}
	}
	for k, srcVal := range src {
		dstVal, exists := dst[k]
		if !exists {
			dst[k] = srcVal
			continue
		}
		srcMap, srcIsMap := srcVal.(map[string]interface{})
		dstMap, dstIsMap := dstVal.(map[string]interface{})
		if srcIsMap && dstIsMap {
			dst[k] = deepMergeMap(dstMap, srcMap)
			continue
		}
		dst[k] = srcVal
	}
	return dst
}
