package packs

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

	"github.com/nexuscrm/kernel/pkg/utils"
)

// connectorRateLimitDefinition is the payload shape for a single rate-limit
// policy declared by a connector pack. The definition is pack-owned (L1);
// the runtime bucket lives in-process only (no separate value table).
type connectorRateLimitDefinition struct {
	// EndpointClass is the logical endpoint group, e.g. "crm.objects.read".
	// Unique within the owning pack (forms part of the UNIQUE constraint with OwnerPack).
	EndpointClass string `json:"endpointClass"`
	// RequestsPerSecond is the sustained fill rate (tokens per second). Must be > 0.
	RequestsPerSecond float64 `json:"requestsPerSecond"`
	// Burst is the maximum instantaneous token reserve. Must be >= 1.
	Burst int `json:"burst"`
	// Description is an optional admin-readable note.
	Description *string `json:"description,omitempty"`
}

// connectorRateLimitProjectionRow is the expected/live projection shape used by
// verifyPackComponentDefault to detect drift.
type connectorRateLimitProjectionRow struct {
	OwnerPack         string  `json:"ownerPack"`
	EndpointClass     string  `json:"endpointClass"`
	RequestsPerSecond float64 `json:"requestsPerSecond"`
	Burst             int     `json:"burst"`
	Description       *string `json:"description,omitempty"`
}

// connectorRateLimitComponent implements PackComponent for ComponentKindConnectorRateLimit.
// It owns exactly one row in _System_ConnectorRateLimit. The component is locked
// (admins cannot override limits — they are L1 product facts).
type connectorRateLimitComponent struct {
	packName   string
	definition connectorRateLimitDefinition
	payload    []byte
}

// NewConnectorRateLimitComponent constructs a connectorRateLimitComponent from a
// parsed definition. Use this from pack-source parsing or test fixtures.
func NewConnectorRateLimitComponent(packName string, def connectorRateLimitDefinition) (PackComponent, error) {
	packName = strings.ToLower(strings.TrimSpace(packName))
	if packName == "" {
		return nil, fmt.Errorf("connector rate-limit component: packName is required")
	}
	def.EndpointClass = strings.ToLower(strings.TrimSpace(def.EndpointClass))
	if def.EndpointClass == "" {
		return nil, fmt.Errorf("connector rate-limit component: endpointClass is required")
	}
	if def.RequestsPerSecond <= 0 {
		return nil, fmt.Errorf("connector rate-limit component: requestsPerSecond must be > 0, got %v", def.RequestsPerSecond)
	}
	if def.Burst < 1 {
		return nil, fmt.Errorf("connector rate-limit component: burst must be >= 1, got %d", def.Burst)
	}
	payload, err := json.Marshal(def)
	if err != nil {
		return nil, fmt.Errorf("connector rate-limit component: marshal payload: %w", err)
	}
	return &connectorRateLimitComponent{
		packName:   packName,
		definition: def,
		payload:    payload,
	}, nil
}

func (c *connectorRateLimitComponent) Kind() ComponentKind {
	return ComponentKindConnectorRateLimit
}

func (c *connectorRateLimitComponent) PackName() string {
	return c.packName
}

func (c *connectorRateLimitComponent) BusinessKey() string {
	return JoinComponentBusinessKey(c.packName, c.definition.EndpointClass)
}

func (c *connectorRateLimitComponent) Manageability() Manageability {
	return ManageabilityLocked
}

func (c *connectorRateLimitComponent) ExpectedPayload() ([]byte, error) {
	return append([]byte(nil), c.payload...), nil
}

func (c *connectorRateLimitComponent) ExpectedProjection() (any, error) {
	return clonePackComponentProjection(connectorRateLimitProjectionRow{
		OwnerPack:         c.packName,
		EndpointClass:     c.definition.EndpointClass,
		RequestsPerSecond: c.definition.RequestsPerSecond,
		Burst:             c.definition.Burst,
		Description:       c.definition.Description,
	})
}

func (c *connectorRateLimitComponent) LiveProjection(ctx context.Context, deps VerifyDeps) (any, error) {
	return liveConnectorRateLimitProjection(ctx, deps.DB, c.packName, c.definition.EndpointClass)
}

func (c *connectorRateLimitComponent) Apply(ctx context.Context, deps ApplyDeps) error {
	return upsertConnectorRateLimitDefinition(ctx, deps.DB, c.packName, c.definition)
}

// upsertConnectorRateLimitDefinition inserts or updates a row in _System_ConnectorRateLimit.
// The UNIQUE (OwnerPack, EndpointClass) constraint ensures idempotent upserts.
// Both ownerPack and endpointClass are normalized to lowercase at intake.
func upsertConnectorRateLimitDefinition(ctx context.Context, db *sql.DB, ownerPack string, def connectorRateLimitDefinition) error {
	normalizedOwnerPack := strings.ToLower(strings.TrimSpace(ownerPack))
	normalizedEndpointClass := strings.ToLower(strings.TrimSpace(def.EndpointClass))
	var descParam interface{}
	if def.Description != nil {
		descParam = *def.Description
	}
	_, err := db.ExecContext(ctx, `
INSERT INTO _System_ConnectorRateLimit (Id, OwnerPack, EndpointClass, RequestsPerSecond, Burst, Description, IsDeleted, CreatedDate, LastModifiedDate)
VALUES (?, ?, ?, ?, ?, ?, 0, NOW(), NOW())
ON DUPLICATE KEY UPDATE
  RequestsPerSecond = VALUES(RequestsPerSecond),
  Burst = VALUES(Burst),
  Description = VALUES(Description),
  IsDeleted = 0,
  LastModifiedDate = NOW()`,
		utils.GenerateID(), normalizedOwnerPack, normalizedEndpointClass, def.RequestsPerSecond, def.Burst, descParam,
	)
	if err != nil {
		return fmt.Errorf("upsert _System_ConnectorRateLimit %s/%s: %w", normalizedOwnerPack, normalizedEndpointClass, err)
	}
	return nil
}

// liveConnectorRateLimitProjection reads the live row from _System_ConnectorRateLimit
// and returns the same projection shape as ExpectedProjection.
func liveConnectorRateLimitProjection(ctx context.Context, db *sql.DB, ownerPack, endpointClass string) (any, error) {
	var row connectorRateLimitProjectionRow
	var description sql.NullString
	err := db.QueryRowContext(ctx, `
SELECT OwnerPack, EndpointClass, RequestsPerSecond, Burst, Description
FROM _System_ConnectorRateLimit
WHERE OwnerPack = ? AND EndpointClass = ? AND IsDeleted = 0
LIMIT 1`, ownerPack, endpointClass).Scan(
		&row.OwnerPack, &row.EndpointClass, &row.RequestsPerSecond, &row.Burst, &description)
	if err == sql.ErrNoRows {
		return nil, fmt.Errorf("connector rate-limit %s/%s: row missing or soft-deleted", ownerPack, endpointClass)
	}
	if err != nil {
		return nil, fmt.Errorf("query connector rate-limit %s/%s: %w", ownerPack, endpointClass, err)
	}
	if description.Valid {
		s := description.String
		row.Description = &s
	}
	return row, nil
}
