package packs

import (
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"
)

// ConnectorCapabilityComponentPayload is the manifest payload for a
// connector_capability component. Namespace MUST equal the containing
// pack's Name (enforced by validateConnectorCapabilityComponent).
// Kind must be one of the two closed-set v1 capability kinds.
type ConnectorCapabilityComponentPayload struct {
	Namespace string `json:"namespace"`
	Kind      string `json:"kind"`
}

// connectorCapabilityKinds is the closed v1 set of capability kinds.
// Adding a third value requires a Connector SDK RFC (coverage matrix Wave 1).
var connectorCapabilityKinds = map[string]struct{}{
	"signature_verifier": {},
	"envelope_parser":    {},
}

// connectorCapabilityDeclarationRow is the DB row shape for
// _System_ConnectorCapabilityDeclaration.
type connectorCapabilityDeclarationRow struct {
	ID               string
	InstalledPackName string
	Namespace        string
	Kind             string
	PayloadHash      string
	IsDeleted        int
}

type connectorCapabilityComponent struct {
	packName          string
	manifestComponent ManifestComponent
	payload           ConnectorCapabilityComponentPayload
	rawPayload        []byte
	payloadHash       string
}

func newConnectorCapabilityComponent(packName string, component ManifestComponent, payload []byte) (PackComponent, error) {
	var p ConnectorCapabilityComponentPayload
	if err := json.Unmarshal(payload, &p); err != nil {
		return nil, fmt.Errorf("parse connector_capability payload %s: %w", componentPayloadLabel(component), err)
	}
	p.Namespace = strings.TrimSpace(p.Namespace)
	p.Kind = strings.TrimSpace(p.Kind)
	hash := connectorCapabilityPayloadHash(p.Namespace, p.Kind)
	return &connectorCapabilityComponent{
		packName:          packName,
		manifestComponent: component,
		payload:           p,
		rawPayload:        append([]byte(nil), payload...),
		payloadHash:       hash,
	}, nil
}

func (c *connectorCapabilityComponent) Kind() ComponentKind {
	return ComponentKindConnectorCapability
}

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

func (c *connectorCapabilityComponent) BusinessKey() string {
	return c.payload.Namespace + ":" + c.payload.Kind
}

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

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

func (c *connectorCapabilityComponent) ExpectedProjection() (any, error) {
	return connectorCapabilityDeclarationRow{
		ID:               connectorCapabilityDeclarationID(c.packName, c.payload.Namespace, c.payload.Kind),
		InstalledPackName: c.packName,
		Namespace:        c.payload.Namespace,
		Kind:             c.payload.Kind,
		PayloadHash:      c.payloadHash,
		IsDeleted:        0,
	}, nil
}

func (c *connectorCapabilityComponent) LiveProjection(ctx context.Context, deps VerifyDeps) (any, error) {
	if deps.DB == nil {
		return nil, fmt.Errorf("database is required")
	}
	var row connectorCapabilityDeclarationRow
	err := deps.DB.QueryRowContext(ctx, `
SELECT Id, InstalledPackName, Namespace, Kind, PayloadHash, COALESCE(IsDeleted, 0)
  FROM _System_ConnectorCapabilityDeclaration
 WHERE InstalledPackName = ? AND Namespace = ? AND Kind = ? AND COALESCE(IsDeleted, 0) = 0
 LIMIT 1`, c.packName, c.payload.Namespace, c.payload.Kind).Scan(
		&row.ID,
		&row.InstalledPackName,
		&row.Namespace,
		&row.Kind,
		&row.PayloadHash,
		&row.IsDeleted,
	)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("query connector capability declaration %s:%s for pack %s: %w", c.payload.Namespace, c.payload.Kind, c.packName, err)
	}
	return row, nil
}

func (c *connectorCapabilityComponent) Apply(ctx context.Context, deps ApplyDeps) error {
	if deps.DB == nil {
		return fmt.Errorf("database is required")
	}
	id := connectorCapabilityDeclarationID(c.packName, c.payload.Namespace, c.payload.Kind)
	_, err := deps.DB.ExecContext(ctx, `
INSERT INTO _System_ConnectorCapabilityDeclaration
  (Id, InstalledPackName, Namespace, Kind, PayloadHash, CreatedDate, LastModifiedDate, IsDeleted)
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0)
ON DUPLICATE KEY UPDATE
  PayloadHash = VALUES(PayloadHash),
  IsDeleted = 0,
  LastModifiedDate = CURRENT_TIMESTAMP`,
		id, c.packName, c.payload.Namespace, c.payload.Kind, c.payloadHash,
	)
	if err != nil {
		return fmt.Errorf("upsert connector capability declaration %s:%s for pack %s: %w", c.payload.Namespace, c.payload.Kind, c.packName, err)
	}
	return nil
}

// connectorCapabilityDeclarationID produces a deterministic row ID from the
// pack + namespace + kind triple. sha256-based so IDs are stable across deploys.
func connectorCapabilityDeclarationID(packName, namespace, kind string) string {
	sum := sha256.Sum256([]byte(
		strings.ToLower(strings.TrimSpace(packName)) + "\x00" +
			strings.ToLower(strings.TrimSpace(namespace)) + "\x00" +
			strings.ToLower(strings.TrimSpace(kind)),
	))
	return "ccd_" + hex.EncodeToString(sum[:])
}

// connectorCapabilityPayloadHash is a stable sha256 of the canonical
// (namespace, kind) payload. Used as the PayloadHash column value so
// apply-vs-verify drift is detectable without re-reading the source tree.
func connectorCapabilityPayloadHash(namespace, kind string) string {
	canonical, _ := json.Marshal(ConnectorCapabilityComponentPayload{
		Namespace: strings.TrimSpace(namespace),
		Kind:      strings.TrimSpace(kind),
	})
	sum := sha256.Sum256(canonical)
	return hex.EncodeToString(sum[:])
}
