package webhooks

import (
	"context"
	"database/sql"
	"strings"
	"time"

	"github.com/pingcap-inc/nexuscrm-connectors/features/namedcredential"
	idgen "github.com/pingcap-inc/nexuscrm-connectors/internal/id"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/contract"
)

// minWebhookSecretLen is the minimum number of runes accepted for a webhook
// secret value on rotate. 16 characters ensures adequate HMAC key entropy
// (production providers issue >= 32-byte hex/b64 secrets).
const minWebhookSecretLen = 16

// WebhookSecretAdminService provides the neutral connector-webhook secret admin
// surface. It owns storage, encryption, and masking; API dialects own route
// binding and response projection.
type WebhookSecretAdminService struct {
	db     *sql.DB
	cipher *namedcredential.SecretCipher
}

// NewWebhookSecretAdminService constructs the neutral admin service. cipher may
// be nil when NEXUSCRM_NC_MASTER_KEY is unset at boot; RotateSecret returns
// ENCRYPTION_UNCONFIGURED in that case (fail closed). ListSecrets degrades
// gracefully by masking as empty when cipher is unavailable.
func NewWebhookSecretAdminService(db *sql.DB, cipher *namedcredential.SecretCipher) *WebhookSecretAdminService {
	return &WebhookSecretAdminService{db: db, cipher: cipher}
}

func (s *WebhookSecretAdminService) ListConnectorWebhookSecrets(ctx context.Context, namespace string) ([]contract.ConnectorWebhookSecret, error) {
	if s == nil || s.db == nil {
		return nil, connectorWebhookSecretsErr(contract.ConnectorWebhookSecretsErrorQueryFailed, "")
	}

	namespace = strings.TrimSpace(namespace)
	query := `
SELECT ws.Id, ws.OwnerPack, ws.Name, ws.Algorithm, ws.Description,
       wsv.SecretValue, wsv.RotatedAt
FROM _System_WebhookSecret ws
LEFT JOIN _System_WebhookSecretValue wsv
  ON wsv.WebhookSecretId = ws.Id AND wsv.IsActive = 1
WHERE COALESCE(ws.IsDeleted, 0) = 0`
	args := []interface{}{}
	if namespace != "" {
		query += " AND ws.OwnerPack = ?"
		args = append(args, namespace)
	}
	query += " ORDER BY ws.OwnerPack, ws.Name"

	rows, err := s.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, connectorWebhookSecretsErrCause(contract.ConnectorWebhookSecretsErrorQueryFailed, "", err)
	}
	defer rows.Close()

	items := make([]contract.ConnectorWebhookSecret, 0)
	for rows.Next() {
		var item contract.ConnectorWebhookSecret
		var secretValue sql.NullString
		var rotatedAt sql.NullTime
		var description sql.NullString
		if err := rows.Scan(
			&item.ID, &item.OwnerPack, &item.Name, &item.Algorithm,
			&description, &secretValue, &rotatedAt,
		); err != nil {
			return nil, connectorWebhookSecretsErrCause(contract.ConnectorWebhookSecretsErrorScanFailed, "", err)
		}
		if description.Valid {
			s := description.String
			item.Description = &s
		}
		if secretValue.Valid && s.cipher != nil {
			plaintext, decErr := s.cipher.Decrypt(secretValue.String)
			if decErr != nil {
				item.MaskedHint = MaskSecretValue("")
			} else {
				item.MaskedHint = MaskSecretValue(plaintext)
			}
		} else {
			item.MaskedHint = MaskSecretValue("")
		}
		if rotatedAt.Valid {
			t := rotatedAt.Time.UTC()
			item.LastRotatedAt = &t
		}
		items = append(items, item)
	}
	if err := rows.Err(); err != nil {
		return nil, connectorWebhookSecretsErrCause(contract.ConnectorWebhookSecretsErrorRowsFailed, "", err)
	}

	return items, nil
}

func (s *WebhookSecretAdminService) RotateConnectorWebhookSecret(ctx context.Context, id string, secretValue string) (*contract.ConnectorWebhookSecretRotation, error) {
	id = strings.TrimSpace(id)
	if id == "" {
		return nil, connectorWebhookSecretsErr(contract.ConnectorWebhookSecretsErrorWebhookSecretNotFound, "")
	}
	trimmedSecret := strings.TrimSpace(secretValue)
	if trimmedSecret == "" {
		return nil, connectorWebhookSecretsErr(contract.ConnectorWebhookSecretsErrorEmptySecretValue, "")
	}
	if len([]rune(trimmedSecret)) < minWebhookSecretLen {
		return nil, &contract.ConnectorWebhookSecretsError{
			Kind:      contract.ConnectorWebhookSecretsErrorSecretTooShort,
			MinLength: minWebhookSecretLen,
		}
	}
	if s == nil || s.db == nil {
		return nil, connectorWebhookSecretsErr(contract.ConnectorWebhookSecretsErrorQueryFailed, "")
	}
	if s.cipher == nil {
		return nil, &contract.ConnectorWebhookSecretsError{
			Kind:   contract.ConnectorWebhookSecretsErrorEncryptionUnconfigured,
			Detail: "webhook secret rotation requires NEXUSCRM_NC_MASTER_KEY",
		}
	}

	ciphertext, err := s.cipher.Encrypt(secretValue)
	if err != nil {
		return nil, connectorWebhookSecretsErrCause(contract.ConnectorWebhookSecretsErrorEncryptionFailed, "", err)
	}

	var exists int
	if err := s.db.QueryRowContext(ctx, `
SELECT 1 FROM _System_WebhookSecret
WHERE Id = ? AND COALESCE(IsDeleted, 0) = 0
LIMIT 1`, id).Scan(&exists); err == sql.ErrNoRows {
		return nil, connectorWebhookSecretsErr(contract.ConnectorWebhookSecretsErrorWebhookSecretNotFound, "")
	} else if err != nil {
		return nil, connectorWebhookSecretsErrCause(contract.ConnectorWebhookSecretsErrorQueryFailed, "", err)
	}

	now := time.Now().UTC()
	valueID := idgen.Generate()
	_, err = s.db.ExecContext(ctx, `
INSERT INTO _System_WebhookSecretValue
  (Id, WebhookSecretId, SecretValue, IsActive, RotatedAt, CreatedDate, LastModifiedDate)
VALUES (?, ?, ?, 1, ?, NOW(), NOW())
ON DUPLICATE KEY UPDATE
  SecretValue = VALUES(SecretValue),
  IsActive = 1,
  RotatedAt = VALUES(RotatedAt),
  LastModifiedDate = NOW()`,
		valueID, id, ciphertext, now,
	)
	if err != nil {
		return nil, connectorWebhookSecretsErrCause(contract.ConnectorWebhookSecretsErrorUpsertFailed, "", err)
	}

	return &contract.ConnectorWebhookSecretRotation{
		ID:         id,
		MaskedHint: MaskSecretValue(secretValue),
		RotatedAt:  now,
	}, nil
}

func connectorWebhookSecretsErr(kind contract.ConnectorWebhookSecretsErrorKind, message string) *contract.ConnectorWebhookSecretsError {
	return &contract.ConnectorWebhookSecretsError{Kind: kind, Message: message}
}

func connectorWebhookSecretsErrCause(kind contract.ConnectorWebhookSecretsErrorKind, message string, cause error) *contract.ConnectorWebhookSecretsError {
	return &contract.ConnectorWebhookSecretsError{Kind: kind, Message: message, Cause: cause}
}

var _ contract.ConnectorWebhookSecrets = (*WebhookSecretAdminService)(nil)
