package bootstrap

import (
	"database/sql"
	"fmt"
	"strings"

	seedrows "github.com/pingcap-inc/nexuscrm-contract/pkg/seedrows"
)

// The standard-action seeding WRITE/INIT/VERIFY surface (Initialize/Verify
// FromPayloads, the satellite upserts and reconcile helpers) was relocated to
// internal/controlplane/packs (Wave 5). The runtime READ surface below STAYS
// here because GetStandardActionInternalAccessScopes is consulted at request
// time by the standard-action dispatcher and is exposed to the standard-action
// service via the contract runtimeports.StandardActionReader port; the read
// path must remain in this composition host.

// Table names consumed by the internal-access-scope reader. These mirror the
// canonical write-side constants (now in internal/controlplane/packs); the
// string literals are byte-identical.
const (
	tableStandardAction                    = "_System_StandardAction"
	tableStandardActionInternalAccessScope = "_System_StandardActionInternalAccessScope"
)

// StandardActionInternalAccessScopeRow is the runtime projection consumed by
// the dispatcher before it invokes a standard action flow. Relocated verbatim
// to the contract module's seedrows package; this alias keeps existing
// importers compiling unchanged.
type StandardActionInternalAccessScopeRow = seedrows.StandardActionInternalAccessScopeRow

type standardActionInternalAccessOwner struct {
	Name              string
	FlowDeveloperName string
	IsCustomized      bool
}

// GetStandardActionInternalAccessScopes returns the runtime internal-access
// scope projection consulted before invoking a standard-action flow.
func GetStandardActionInternalAccessScopes(db *sql.DB, standardActionID string) ([]StandardActionInternalAccessScopeRow, error) {
	if db == nil || strings.TrimSpace(standardActionID) == "" {
		return nil, nil
	}
	// Runtime authorization reads the verified metadata substrate as the SSoT.
	// The CRM pack payload is intentionally consulted by apply/verify only, so
	// generic runtime code does not carry a second embedded L1 read path.
	owner, err := loadStandardActionInternalAccessOwner(db, standardActionID)
	if err != nil {
		return nil, err
	}
	if owner == nil || owner.IsCustomized {
		return nil, nil
	}
	flowPackOwned, err := StandardFlowActiveVersionIsPackOwned(db, owner.FlowDeveloperName)
	if err != nil {
		return nil, err
	}
	if !flowPackOwned {
		return nil, nil
	}
	rows, err := db.Query(
		fmt.Sprintf(
			`SELECT StandardActionId, ObjectApiName, Operation, Reason
			   FROM %s
			  WHERE StandardActionId = ? AND IsActive = 1 AND IsDeleted = 0
			  ORDER BY ObjectApiName, Operation`,
			tableStandardActionInternalAccessScope),
		standardActionID,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var out []StandardActionInternalAccessScopeRow
	for rows.Next() {
		var row StandardActionInternalAccessScopeRow
		var reason sql.NullString
		if err := rows.Scan(&row.StandardActionID, &row.ObjectAPIName, &row.Operation, &reason); err != nil {
			return nil, err
		}
		if reason.Valid {
			row.Reason = reason.String
		}
		out = append(out, row)
	}
	return out, rows.Err()
}

func loadStandardActionInternalAccessOwner(db *sql.DB, standardActionID string) (*standardActionInternalAccessOwner, error) {
	var owner standardActionInternalAccessOwner
	var flowName sql.NullString
	var isCustomized sql.NullBool
	err := db.QueryRow(
		fmt.Sprintf(
			`SELECT Name, FlowDeveloperName, IsCustomized
			   FROM %s
			  WHERE Id = ? AND IsDeleted = 0`,
			tableStandardAction),
		standardActionID,
	).Scan(&owner.Name, &flowName, &isCustomized)
	switch {
	case err == sql.ErrNoRows:
		return nil, nil
	case err != nil:
		return nil, err
	}
	if flowName.Valid {
		owner.FlowDeveloperName = flowName.String
	}
	if isCustomized.Valid {
		owner.IsCustomized = isCustomized.Bool
	}
	return &owner, nil
}
