package daemon

import (
	"errors"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/daemoncontrol"
	"github.com/tidbcloud/AgentTeam/x/proc"
)

func TestResolveRunOptionsOwnerMatrix(t *testing.T) {
	current, found := proc.ProcessByPID(os.Getpid())
	require.True(t, found)
	require.False(t, current.StartedAt.IsZero())

	tests := []struct {
		name     string
		options  RunOptions
		wantKind daemoncontrol.OwnerKind
		wantID   string
		wantPID  int
		wantBind BindIntent
		wantErr  string
	}{
		{
			name: "CLI defaults owner identity and dynamic loopback bind",
			options: RunOptions{
				OwnerBootstrap: OwnerBootstrap{Kind: daemoncontrol.OwnerKindCLI},
				BindIntent:     BindIntent{},
			},
			wantKind: daemoncontrol.OwnerKindCLI,
			wantPID:  os.Getpid(),
			wantBind: BindIntent{Host: "127.0.0.1", Port: 0},
		},
		{
			name: "service accepts explicit loopback bind",
			options: RunOptions{
				OwnerBootstrap: OwnerBootstrap{
					Kind: daemoncontrol.OwnerKindService,
					ID:   "service-owner",
				},
				BindIntent: BindIntent{Host: "::1", Port: 43123},
			},
			wantKind: daemoncontrol.OwnerKindService,
			wantID:   "service-owner",
			wantPID:  os.Getpid(),
			wantBind: BindIntent{Host: "::1", Port: 43123},
		},
		{
			name: "electron requires owner ID",
			options: RunOptions{
				OwnerBootstrap: OwnerBootstrap{
					Kind: daemoncontrol.OwnerKindElectron,
					PID:  os.Getpid(),
				},
				BindIntent: BindIntent{Host: "127.0.0.1", Port: 0},
			},
			wantErr: "owner id",
		},
		{
			name: "electron requires owner PID",
			options: RunOptions{
				OwnerBootstrap: OwnerBootstrap{
					Kind: daemoncontrol.OwnerKindElectron,
					ID:   "electron-owner",
				},
				BindIntent: BindIntent{Host: "127.0.0.1", Port: 0},
			},
			wantErr: "owner pid",
		},
		{
			name: "electron requires exact dynamic IPv4 loopback bind",
			options: RunOptions{
				OwnerBootstrap: OwnerBootstrap{
					Kind: daemoncontrol.OwnerKindElectron,
					ID:   "electron-owner",
					PID:  os.Getpid(),
				},
				BindIntent: BindIntent{Host: "localhost", Port: 0},
			},
			wantErr: "electron bind",
		},
		{
			name: "rejects unsupported owner",
			options: RunOptions{
				OwnerBootstrap: OwnerBootstrap{Kind: "browser"},
			},
			wantErr: "owner kind",
		},
		{
			name: "rejects non-loopback bind",
			options: RunOptions{
				OwnerBootstrap: OwnerBootstrap{Kind: daemoncontrol.OwnerKindCLI},
				BindIntent:     BindIntent{Host: "0.0.0.0", Port: 9090},
			},
			wantErr: "loopback",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			resolved, err := resolveRunOptions(tt.options)
			if tt.wantErr != "" {
				require.ErrorContains(t, err, tt.wantErr)
				return
			}
			require.NoError(t, err)
			require.Equal(t, tt.wantKind, resolved.owner.Kind)
			require.Equal(t, tt.wantPID, resolved.owner.PID)
			require.Equal(t, current.StartedAt, resolved.owner.StartedAt)
			require.Equal(t, current.StartID, resolved.owner.StartID)
			require.Equal(t, tt.wantBind, resolved.bind)
			if tt.wantID == "" {
				require.NotEmpty(t, resolved.owner.ID)
			} else {
				require.Equal(t, tt.wantID, resolved.owner.ID)
			}
		})
	}
}

func TestResolveRunOptionsRejectsUnknownOwnerProcess(t *testing.T) {
	_, err := resolveRunOptions(RunOptions{
		OwnerBootstrap: OwnerBootstrap{
			Kind: daemoncontrol.OwnerKindElectron,
			ID:   "electron-owner",
			PID:  maxProcessIDForTest(),
		},
		BindIntent: BindIntent{Host: "127.0.0.1", Port: 0},
	})
	require.ErrorContains(t, err, "owner process")
}

func maxProcessIDForTest() int {
	if ^uint(0)>>63 == 1 {
		return int(^uint32(0) >> 1)
	}
	return 1<<30 - 1
}

func TestNewDaemonReceiptUsesRandomInstanceAndControlCredentials(t *testing.T) {
	startedAt := time.Now().UTC().Truncate(time.Second)
	owner := daemoncontrol.Owner{
		Kind: daemoncontrol.OwnerKindCLI,
		ID:   "cli-owner",
		ProcessIdentity: daemoncontrol.ProcessIdentity{
			PID:       os.Getpid(),
			StartedAt: startedAt,
		},
	}
	first, err := newDaemonReceipt(owner, "http://127.0.0.1:43123")
	require.NoError(t, err)
	second, err := newDaemonReceipt(owner, "http://127.0.0.1:43123")
	require.NoError(t, err)

	require.NotEqual(t, first.InstanceID, second.InstanceID)
	require.NotEqual(t, first.ControlToken, second.ControlToken)
	require.NotEmpty(t, first.InstanceID)
	require.NotEmpty(t, first.ControlToken)
	require.Equal(t, owner, first.Owner)
	require.Equal(t, os.Getpid(), first.PID)
	require.False(t, first.StartedAt.IsZero())
	current, found := proc.ProcessByPID(os.Getpid())
	require.True(t, found)
	require.Equal(t, current.StartID, first.StartID)
	require.NoError(t, first.Validate())
}

func TestShutdownDoesNotDeleteReceiptBeforePublication(t *testing.T) {
	runtimeDir := t.TempDir()
	store := daemoncontrol.NewReceiptStore(runtimeDir)
	receipt := validRunTestReceipt(t)
	require.NoError(t, store.Write(receipt))

	d := &Daemon{
		receiptStore: store,
		receipt:      &receipt,
	}
	d.shutdown()

	got, err := store.Read()
	require.NoError(t, err)
	require.Equal(t, receipt.InstanceID, got.InstanceID)
}

func TestShutdownDeletesOnlyPublishedMatchingReceipt(t *testing.T) {
	runtimeDir := t.TempDir()
	store := daemoncontrol.NewReceiptStore(runtimeDir)
	receipt := validRunTestReceipt(t)
	require.NoError(t, store.Write(receipt))

	d := &Daemon{
		receiptStore:     store,
		receipt:          &receipt,
		receiptPublished: true,
	}
	d.shutdown()

	_, err := store.Read()
	require.True(t, errors.Is(err, daemoncontrol.ErrReceiptMissing), "read error = %v", err)
}

func validRunTestReceipt(t *testing.T) daemoncontrol.Receipt {
	t.Helper()
	current, found := proc.ProcessByPID(os.Getpid())
	require.True(t, found)
	receipt := daemoncontrol.Receipt{
		SchemaVersion:     daemoncontrol.SchemaVersion,
		DesktopAPIVersion: daemoncontrol.DesktopAPIVersion,
		InstanceID:        "instance-run-test",
		PID:               current.PID,
		StartedAt:         current.StartedAt,
		StartID:           current.StartID,
		BaseURL:           "http://127.0.0.1:43123",
		Owner: daemoncontrol.Owner{
			Kind: daemoncontrol.OwnerKindCLI,
			ID:   "owner-run-test",
			ProcessIdentity: daemoncontrol.ProcessIdentity{
				PID:       current.PID,
				StartedAt: current.StartedAt,
				StartID:   current.StartID,
			},
		},
		ControlToken: "control-run-test",
	}
	require.NoError(t, receipt.Validate())
	return receipt
}
