package agentstate

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestWakeDeliveryValidateRequiresTypedTerminalEvidence(t *testing.T) {
	now := time.Date(2026, 8, 19, 1, 2, 3, 0, time.UTC)
	base := WakeDeliveryState{
		LocalWakeID:     "wake-1",
		SourceMessageID: "msg-1",
		TargetKey:       "#agentteam:thread",
		AckedAt:         now,
		ChangedAt:       now,
	}

	confirmed := base
	confirmed.Phase = WakePhaseReplyConfirmed
	require.Error(t, confirmed.Validate())
	confirmed.ReplyObjectIDs = []string{"msg-reply"}
	require.NoError(t, confirmed.Validate())

	noReply := base
	noReply.Phase = WakePhaseNoReply
	require.Error(t, noReply.Validate())
	noReply.NoReplyReasonCode = WakeNoReplyDecided
	require.NoError(t, noReply.Validate())

	failed := base
	failed.Phase = WakePhaseFailed
	require.Error(t, failed.Validate())
	failed.FailureReasonCode = WakeFailureRuntimeFailed
	require.NoError(t, failed.Validate())
}

func TestWakeDeliveryJSONUsesOneTypedReasonCode(t *testing.T) {
	state := validWakeDeliveryForModelTest(WakePhaseNoReply)
	state.NoReplyReasonCode = WakeNoReplyDecided

	raw, err := json.Marshal(state)
	require.NoError(t, err)
	require.JSONEq(t, `{
		"localWakeId":"wake-1",
		"sourceMessageId":"msg-1",
		"targetKey":"#agentteam:thread",
		"surfacedAt":"2026-08-19T01:02:03Z",
		"changedAt":"2026-08-19T01:02:03Z",
		"phase":"no_reply",
		"reasonCode":"no_reply_decided"
	}`, string(raw))
	require.NotContains(t, string(raw), "transmissionBoundary")

	var restored WakeDeliveryState
	require.NoError(t, json.Unmarshal(raw, &restored))
	require.Equal(t, WakeNoReplyDecided, restored.NoReplyReasonCode)
	require.Empty(t, restored.FailureReasonCode)
	require.NoError(t, restored.Validate())
}

func TestWakeDeliveryJSONReadsAlternateAckedAtAndWritesFrozenSurfacedAt(t *testing.T) {
	var restored WakeDeliveryState
	require.NoError(t, json.Unmarshal([]byte(`{
		"localWakeId":"wake-1",
		"sourceMessageId":"msg-1",
		"targetKey":"#agentteam:thread",
		"ackedAt":"2026-08-19T01:02:03Z",
		"changedAt":"2026-08-19T01:02:03Z",
		"phase":"no_reply",
		"reasonCode":"no_reply_decided"
	}`), &restored))
	require.NoError(t, restored.Validate())

	raw, err := json.Marshal(restored)
	require.NoError(t, err)
	require.Contains(t, string(raw), `"surfacedAt":"2026-08-19T01:02:03Z"`)
	require.NotContains(t, string(raw), `"ackedAt"`)
}

func TestAgentStateSnapshotJSONContract(t *testing.T) {
	now := time.Date(2026, 8, 19, 1, 2, 3, 0, time.UTC)
	snapshot := AgentStateSnapshotV2{
		EnvelopeBaseV2: EnvelopeBaseV2{
			Version:      ProtocolVersion,
			AgentID:      "agt-1",
			SessionEpoch: 7,
			Revision:     1,
			EmittedAt:    now,
		},
		Mode: SnapshotMode,
		Execution: AgentExecutionState{
			Phase:     ExecutionIdle,
			ChangedAt: now,
		},
		Deliveries:  []WakeDeliveryState{},
		Diagnostics: []AgentDiagnostic{},
	}

	require.NoError(t, snapshot.Validate())
	raw, err := json.Marshal(snapshot)
	require.NoError(t, err)
	require.JSONEq(t, `{
		"version":2,
		"agentId":"agt-1",
		"sessionEpoch":7,
		"revision":1,
		"emittedAt":"2026-08-19T01:02:03Z",
		"mode":"snapshot",
		"execution":{
			"phase":"idle",
			"changedAt":"2026-08-19T01:02:03Z"
		},
		"deliveries":[],
		"diagnostics":[]
	}`, string(raw))
	require.NotContains(t, string(raw), "detail")
}

func TestAgentDiagnosticValidateTypedShape(t *testing.T) {
	now := time.Date(2026, 8, 19, 1, 2, 3, 0, time.UTC)

	reconcile := AgentDiagnostic{
		DiagnosticID:      "diag-1",
		TargetKey:         "#agentteam",
		LocalWakeIDs:      []string{"wake-1"},
		ClientOperationID: "op-1",
		CreatedAt:         now,
		Category:          DiagnosticDelivery,
		Terminal:          false,
		DeliveryReason:    DeliveryDiagnosticReceiptReconcilePending,
	}
	require.NoError(t, reconcile.Validate())

	reconcile.TargetKey = ""
	require.ErrorContains(t, reconcile.Validate(), "target")

	globalExecution := AgentDiagnostic{
		DiagnosticID:    "diag-2",
		CreatedAt:       now,
		Category:        DiagnosticExecution,
		Terminal:        true,
		ExecutionReason: ExecutionReasonRuntimeFailed,
	}
	require.NoError(t, globalExecution.Validate())

	globalExecution.LocalWakeIDs = []string{"wake-1"}
	require.ErrorContains(t, globalExecution.Validate(), "target")
}

func validWakeDeliveryForModelTest(phase WakeDeliveryPhase) WakeDeliveryState {
	now := time.Date(2026, 8, 19, 1, 2, 3, 0, time.UTC)
	state := WakeDeliveryState{
		LocalWakeID:     "wake-1",
		SourceMessageID: "msg-1",
		TargetKey:       "#agentteam:thread",
		AckedAt:         now,
		ChangedAt:       now,
		Phase:           phase,
	}
	switch phase {
	case WakePhaseReplyConfirmed:
		state.ReplyObjectIDs = []string{"msg-reply"}
	case WakePhaseNoReply:
		state.NoReplyReasonCode = WakeNoReplyDecided
	case WakePhaseFailed:
		state.FailureReasonCode = WakeFailureRuntimeFailed
	}
	return state
}
