package dialectsfcrm

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/dialect"
)

func TestConnectorWebhookSecretsListProjectsMaskedOnly(t *testing.T) {
	description := "Orders endpoint"
	rotated := time.Date(2026, 1, 2, 3, 4, 5, 0, time.FixedZone("CET", 3600))
	resp, err := ConnectorWebhookSecretsListNeutralHandler(context.Background(), &dialect.Request{
		Query: map[string][]string{"namespace": {"my-pack"}},
	}, connectorWebhookSecretsDeps(&fakeConnectorWebhookSecrets{
		secrets: []contract.ConnectorWebhookSecret{{
			ID:            "def-001",
			OwnerPack:     "my-pack",
			Name:          "order-webhook",
			Algorithm:     "HMAC-SHA256",
			Description:   &description,
			MaskedHint:    "whsec_•••alue",
			LastRotatedAt: &rotated,
		}},
	}))
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != 200 {
		t.Fatalf("status = %d, want 200", resp.Status)
	}
	body := marshalJSON(t, resp.Body)
	for _, want := range []string{
		`"webhookSecrets"`,
		`"id":"def-001"`,
		`"ownerPack":"my-pack"`,
		`"name":"order-webhook"`,
		`"algorithm":"HMAC-SHA256"`,
		`"description":"Orders endpoint"`,
		`"masked":"whsec_•••alue"`,
		`"lastRotatedAt":"2026-01-02T02:04:05Z"`,
	} {
		if !strings.Contains(body, want) {
			t.Fatalf("body missing %s: %s", want, body)
		}
	}
	if strings.Contains(body, "SecretValue") || strings.Contains(body, "supersecret") {
		t.Fatalf("body leaked secret material: %s", body)
	}
}

func TestConnectorWebhookSecretsRotateSuccess(t *testing.T) {
	rotated := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
	fake := &fakeConnectorWebhookSecrets{
		rotation: &contract.ConnectorWebhookSecretRotation{
			ID:         "def-001",
			MaskedHint: "whsec_•••cret",
			RotatedAt:  rotated,
		},
	}
	resp, err := ConnectorWebhookSecretsRotateNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"id": "def-001"},
		Body:   []byte(`{"secretValue":"newvalue-long-enough-secret"}`),
	}, connectorWebhookSecretsDeps(fake))
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
	if resp.Status != 200 {
		t.Fatalf("status = %d, want 200", resp.Status)
	}
	if fake.rotateID != "def-001" || fake.rotateSecret != "newvalue-long-enough-secret" {
		t.Fatalf("contract called with id=%q secret=%q", fake.rotateID, fake.rotateSecret)
	}
	body := marshalJSON(t, resp.Body)
	for _, want := range []string{`"id":"def-001"`, `"masked":"whsec_•••cret"`, `"rotatedAt":"2026-01-02T03:04:05Z"`} {
		if !strings.Contains(body, want) {
			t.Fatalf("body missing %s: %s", want, body)
		}
	}
	if strings.Contains(body, "newvalue-long-enough-secret") {
		t.Fatalf("body leaked raw secret: %s", body)
	}
}

func TestConnectorWebhookSecretsRotateErrors(t *testing.T) {
	tests := []struct {
		name       string
		req        *dialect.Request
		err        error
		wantStatus int
		wantParts  []string
	}{
		{
			name:       "missing id",
			req:        &dialect.Request{Body: []byte(`{"secretValue":"newvalue-long-enough-secret"}`)},
			wantStatus: 400,
			wantParts:  []string{`"error":"MISSING_ID"`},
		},
		{
			name:       "missing secret value is invalid body",
			req:        &dialect.Request{Params: map[string]string{"id": "def-001"}, Body: []byte(`{}`)},
			wantStatus: 400,
			wantParts:  []string{`"error":"INVALID_BODY"`, `"detail":"Key: 'rotateRequest.SecretValue' Error:Field validation for 'SecretValue' failed on the 'required' tag"`},
		},
		{
			name:       "short secret",
			req:        &dialect.Request{Params: map[string]string{"id": "def-001"}, Body: []byte(`{"secretValue":"tooshort"}`)},
			err:        &contract.ConnectorWebhookSecretsError{Kind: contract.ConnectorWebhookSecretsErrorSecretTooShort, MinLength: 16},
			wantStatus: 400,
			wantParts:  []string{`"error":"SECRET_TOO_SHORT"`, `"minLength":16`},
		},
		{
			name:       "encryption unconfigured",
			req:        &dialect.Request{Params: map[string]string{"id": "def-001"}, Body: []byte(`{"secretValue":"newvalue-long-enough-secret"}`)},
			err:        &contract.ConnectorWebhookSecretsError{Kind: contract.ConnectorWebhookSecretsErrorEncryptionUnconfigured, Detail: "webhook secret rotation requires NEXUSCRM_NC_MASTER_KEY"},
			wantStatus: 500,
			wantParts:  []string{`"error":"ENCRYPTION_UNCONFIGURED"`, `"detail":"webhook secret rotation requires NEXUSCRM_NC_MASTER_KEY"`},
		},
		{
			name:       "not found",
			req:        &dialect.Request{Params: map[string]string{"id": "def-001"}, Body: []byte(`{"secretValue":"newvalue-long-enough-secret"}`)},
			err:        &contract.ConnectorWebhookSecretsError{Kind: contract.ConnectorWebhookSecretsErrorWebhookSecretNotFound},
			wantStatus: 404,
			wantParts:  []string{`"error":"WEBHOOK_SECRET_NOT_FOUND"`},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			resp, err := ConnectorWebhookSecretsRotateNeutralHandler(context.Background(), tc.req, connectorWebhookSecretsDeps(&fakeConnectorWebhookSecrets{rotateErr: tc.err}))
			if err != nil {
				t.Fatalf("handler returned error: %v", err)
			}
			if resp.Status != tc.wantStatus {
				t.Fatalf("status = %d, want %d", resp.Status, tc.wantStatus)
			}
			body := marshalJSON(t, resp.Body)
			for _, part := range tc.wantParts {
				if !strings.Contains(body, part) {
					t.Fatalf("body missing %s: %s", part, body)
				}
			}
		})
	}
}

func connectorWebhookSecretsDeps(secrets contract.ConnectorWebhookSecrets) dialect.Deps {
	return dialect.Deps{Contracts: contract.Deps{ConnectorWebhookSecrets: secrets}}
}

type fakeConnectorWebhookSecrets struct {
	secrets      []contract.ConnectorWebhookSecret
	listErr      error
	rotation     *contract.ConnectorWebhookSecretRotation
	rotateErr    error
	rotateID     string
	rotateSecret string
}

func (f *fakeConnectorWebhookSecrets) ListConnectorWebhookSecrets(context.Context, string) ([]contract.ConnectorWebhookSecret, error) {
	if f.listErr != nil {
		return nil, f.listErr
	}
	return f.secrets, nil
}

func (f *fakeConnectorWebhookSecrets) RotateConnectorWebhookSecret(_ context.Context, id string, secretValue string) (*contract.ConnectorWebhookSecretRotation, error) {
	f.rotateID = id
	f.rotateSecret = secretValue
	if f.rotateErr != nil {
		return nil, f.rotateErr
	}
	return f.rotation, nil
}
