package agent_test

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api/agent"
	"github.com/tidbcloud/AgentTeam/internal/api/daemon"
	"github.com/tidbcloud/AgentTeam/internal/api/testkit"
	"github.com/tidbcloud/AgentTeam/internal/archive"
	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/provider"
	"github.com/tidbcloud/AgentTeam/internal/trigger"
)

func TestInternalAgentTriggerManagementEndpoints(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	_, pool := startAgentAPITestPool(t, ctx)

	archiveStorage, err := archive.NewFilesystem(t.TempDir())
	require.NoError(t, err)
	dataStore := testkit.NewWithOptions(pool, archiveStorage, 30, testkit.StoreOptions{
		AppCredentialStorageKey: []byte("0123456789abcdef0123456789abcdef"),
	})
	taskStore := mustTaskClient(t, pool)

	logger := slog.New(slog.NewTextHandler(io.Discard, nil))
	service := agent.New(
		logger,
		dataStore.AgentStorePortsDeps(),
		dataStore.Attachment(),
		taskStore,
		daemon.New(logger, daemon.NewStore(newAgentRuntime(pool, dataStore).Agents(), newAgentRuntime(pool, dataStore).Recovery(), newAgentRuntime(pool, dataStore).PrepareAgentStart), "http://example.com", nil),
		nil,
	)
	fixedNow := time.Date(2026, 4, 18, 10, 0, 0, 0, time.UTC)
	service.TriggerService = trigger.New(logger, pool, dataStore.App(), trigger.ServiceOptions{
		Clock: func() time.Time { return fixedNow },
	})

	gin.SetMode(gin.TestMode)
	router := gin.New()
	agent.RegisterRoutes(router, service)

	now := fixedNow
	workspaceID := "wsp_trigger_agent"
	userID := "usr_0000000trigger_owner"
	humanMemberID := "twsx4e519rvs"
	agentMemberID := "t1h3p0edr4uq"
	channelID := "chn_trigger_all"
	machineID := "mac_trigger_owner"
	agentID := "agt_0000000trigger_owner"
	agentToken := "trigger-agent-token"

	mustExec(t, pool, `
INSERT INTO users (id, email, name, display_name, avatar_url, is_admin, password_hash, email_verified_at, created_at, updated_at)
VALUES ($1, 'trigger-agent@example.com', 'trigger-agent-owner', 'Trigger Agent Owner', '', TRUE, 'hash', $2, $2, $2)
`, userID, now)
	organizationID := seedAgentTestWorkspaceOrganization(t, pool, workspaceID, "trigger-agent", userID, now)
	mustExec(t, pool, `
INSERT INTO workspaces (id, organization_id, slug, name, latest_message_seq, created_at, updated_at)
VALUES ($1, $2, 'trigger-agent', 'trigger-agent', 0, $3, $3)
`, workspaceID, organizationID, now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, user_id, workspace_role, created_at, updated_at)
VALUES ($1, $2, 'human', 'Owner', $3, 'owner', $4, $4)
`, humanMemberID, workspaceID, userID, now)
	mustExec(t, pool, `
INSERT INTO members (id, workspace_id, type, display_name, created_at, updated_at)
VALUES ($1, $2, 'agent', 'Trigger App', $3, $3)
`, agentMemberID, workspaceID, now)
	mustExec(t, pool, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, 'Trigger machine', 'machine-key', $4, $4)
`, machineID, workspaceID, userID, now)
	mustExec(t, pool, `
INSERT INTO agents (id, workspace_id, machine_id, member_id, name, runtime, model, description, auth_token_hash, activity_detail, created_at, updated_at)
VALUES ($1, $2, $3, $4, 'trigger-agent', 'codex', 'gpt-5', '', $5, '', $6, $6)
`, agentID, workspaceID, machineID, agentMemberID, secrets.HashToken(agentToken), now)
	seedAgentTestChannelScope(t, pool, workspaceID, channelID, "all", humanMemberID, now)
	seedAgentTestPublicScopeMemberships(t, pool, channelID, now, humanMemberID, agentMemberID)

	validateResponse := doAgentJSONMethodRequest(t, router, http.MethodPost, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers/validate", map[string]any{
		"name": "Deploy reminder",
		"source": map[string]any{
			"sourceKind":      "time",
			"timeMode":        "one_shot_relative",
			"relativeSeconds": 1800,
		},
		"target":          "#all",
		"messageTemplate": "Deploy is still running?",
	})
	require.Equal(t, http.StatusOK, validateResponse.Code, validateResponse.Body.String())

	var preview trigger.ValidateResult
	require.NoError(t, json.Unmarshal(validateResponse.Body.Bytes(), &preview))
	require.Nil(t, preview.Current)
	require.True(t, preview.Proposed.Validation.Valid, "%+v", preview.Proposed.Validation)
	require.NotNil(t, preview.EstimatedScheduledAt)
	require.Equal(t, trigger.TargetKindChannel, preview.Proposed.Target.Kind)

	createResponse := doAgentJSONMethodRequest(t, router, http.MethodPost, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers", map[string]any{
		"name": "Friday follow-up",
		"source": map[string]any{
			"sourceKind":     "time",
			"timeMode":       "cron",
			"cronExpression": "15 10 * * 5",
			"timezone":       "UTC",
		},
		"target":          "#all",
		"messageTemplate": "Friday sync?",
	})
	require.Equal(t, http.StatusOK, createResponse.Code, createResponse.Body.String())

	var created trigger.TriggerView
	require.NoError(t, json.Unmarshal(createResponse.Body.Bytes(), &created))
	require.NotEmpty(t, created.ID)
	require.Equal(t, trigger.StatusActive, created.Status)
	require.NotNil(t, created.NextOccurrenceAt)

	listResponse := doAgentJSONMethodRequest(t, router, http.MethodGet, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers", nil)
	require.Equal(t, http.StatusOK, listResponse.Code, listResponse.Body.String())

	require.NotContains(t, listResponse.Body.String(), "messageTemplate")
	require.NotContains(t, listResponse.Body.String(), "Friday sync?")

	var listed []trigger.TriggerSummaryView
	require.NoError(t, json.Unmarshal(listResponse.Body.Bytes(), &listed))
	require.Len(t, listed, 1)
	require.Equal(t, created.ID, listed[0].ID)

	getResponse := doAgentJSONMethodRequest(t, router, http.MethodGet, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers/"+created.ID, nil)
	require.Equal(t, http.StatusOK, getResponse.Code, getResponse.Body.String())

	var detail trigger.TriggerView
	require.NoError(t, json.Unmarshal(getResponse.Body.Bytes(), &detail))
	require.Equal(t, "Friday sync?", detail.MessageTemplate)
	require.True(t, detail.Validation.Valid)

	updateResponse := doAgentJSONMethodRequest(t, router, http.MethodPatch, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers/"+created.ID, map[string]any{
		"name": "Friday follow-up",
		"source": map[string]any{
			"sourceKind":     "time",
			"timeMode":       "cron",
			"cronExpression": "30 10 * * 5",
			"timezone":       "UTC",
		},
		"target":          "#all",
		"messageTemplate": "Updated Friday sync?",
	})
	require.Equal(t, http.StatusOK, updateResponse.Code, updateResponse.Body.String())

	var updated trigger.TriggerView
	require.NoError(t, json.Unmarshal(updateResponse.Body.Bytes(), &updated))
	require.Equal(t, "30 10 * * 5", updated.CronExpression)
	require.Equal(t, "Updated Friday sync?", updated.MessageTemplate)

	runsResponse := doAgentJSONMethodRequest(t, router, http.MethodGet, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers/"+created.ID+"/runs?limit=3", nil)
	require.Equal(t, http.StatusOK, runsResponse.Code, runsResponse.Body.String())

	var runs []trigger.RunView
	require.NoError(t, json.Unmarshal(runsResponse.Body.Bytes(), &runs))
	require.Empty(t, runs)

	pauseResponse := doAgentJSONMethodRequest(t, router, http.MethodPost, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers/"+created.ID+"/pause", map[string]any{})
	require.Equal(t, http.StatusOK, pauseResponse.Code, pauseResponse.Body.String())

	var paused trigger.TriggerView
	require.NoError(t, json.Unmarshal(pauseResponse.Body.Bytes(), &paused))
	require.Equal(t, trigger.StatusPaused, paused.Status)

	resumeResponse := doAgentJSONMethodRequest(t, router, http.MethodPost, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers/"+created.ID+"/resume", map[string]any{})
	require.Equal(t, http.StatusOK, resumeResponse.Code, resumeResponse.Body.String())

	var resumed trigger.TriggerView
	require.NoError(t, json.Unmarshal(resumeResponse.Body.Bytes(), &resumed))
	require.Equal(t, trigger.StatusActive, resumed.Status)

	deleteResponse := doAgentJSONMethodRequest(t, router, http.MethodDelete, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers/"+created.ID, nil)
	require.Equal(t, http.StatusOK, deleteResponse.Code, deleteResponse.Body.String())

	var deleted struct {
		Deleted bool `json:"deleted"`
	}
	require.NoError(t, json.Unmarshal(deleteResponse.Body.Bytes(), &deleted))
	require.True(t, deleted.Deleted)

	installation, err := setupGitHubAppForWorkspaceAdminForTest(ctx, dataStore.App(), userID, workspaceID, provider.UpsertGitHubAppConfigParams{
		AppID:         4242,
		AppSlug:       "agentteam-http",
		AppName:       "AgentTeam HTTP",
		HTMLURL:       "https://github.com/apps/agentteam-http",
		ClientID:      "Iv1.http",
		ClientSecret:  "client-secret",
		PrivateKeyPEM: "-----BEGIN RSA PRIVATE KEY-----\nkey\n-----END RSA PRIVATE KEY-----",
		WebhookSecret: "webhook-secret",
		OwnerLogin:    "acme",
		OwnerID:       7,
	})
	require.NoError(t, err)

	connection, err := dataStore.App().UpsertAppConnection(ctx, app.UpsertAppConnectionParams{
		InstallationID:     installation.ID,
		ExternalSubjectRef: "789",
		DisplayLabel:       "acme",
		Credentials:        map[string]any{"webhook_secret": "webhook-secret"},
		CreatedByUserID:    userID,
	})
	require.NoError(t, err)

	eventValidateResponse := doAgentJSONMethodRequest(t, router, http.MethodPost, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers/validate", map[string]any{
		"name": "GitHub watch",
		"source": map[string]any{
			"sourceKind": "event",
			"eventSubscription": map[string]any{
				"eventKinds": []string{"github_pull_request.*"},
				"filter": map[string]any{
					"resourceIdentifier": "github:acme/agentteam#42",
					"connectionRef":      connection.ExternalSubjectRef,
				},
			},
		},
		"target":          "#all",
		"messageTemplate": "{{event_summary}}\n{{resource_url}}{{external_body_blockquote}}",
	})
	require.Equal(t, http.StatusBadRequest, eventValidateResponse.Code, eventValidateResponse.Body.String())
	require.Contains(t, eventValidateResponse.Body.String(), "GitHub/Gitea provider watches must be managed through the app page or authorized provider app MCP")

	eventCreateResponse := doAgentJSONMethodRequest(t, router, http.MethodPost, agentID, agentToken, "/api/v1/workspaces/"+workspaceID+"/agents/"+agentID+"/runtime/triggers", map[string]any{
		"name": "GitHub watch",
		"source": map[string]any{
			"sourceKind": "event",
			"eventSubscription": map[string]any{
				"eventKinds": []string{"github_pull_request.*"},
				"filter": map[string]any{
					"resourceIdentifier": "github:acme/agentteam#42",
					"connectionRef":      connection.ExternalSubjectRef,
				},
			},
		},
		"target":          "#all",
		"messageTemplate": "{{event_summary}}\n{{resource_url}}{{external_body_blockquote}}",
	})
	require.Equal(t, http.StatusBadRequest, eventCreateResponse.Code, eventCreateResponse.Body.String())
	require.Contains(t, eventCreateResponse.Body.String(), "GitHub/Gitea provider watches must be managed through the app page or authorized provider app MCP")
}

func doAgentJSONMethodRequest(t *testing.T, router http.Handler, method string, agentID string, token string, path string, payload any) *httptest.ResponseRecorder {
	t.Helper()

	var body io.Reader
	if payload != nil {
		encoded, err := json.Marshal(payload)
		require.NoError(t, err)
		body = bytes.NewReader(encoded)
	}

	request := httptest.NewRequest(method, path, body)
	if payload != nil {
		request.Header.Set("Content-Type", "application/json")
	}
	request.Header.Set("Authorization", "Bearer "+token)
	request.Header.Set("X-Agent-Id", agentID)

	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	return response
}
