package server

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	agsauth "github.com/pingcap/agent-git-service/auth"
	agsconfig "github.com/pingcap/agent-git-service/config"
	agsserver "github.com/pingcap/agent-git-service/server"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/agsbridge"
	"github.com/tidbcloud/AgentTeam/internal/api/ags"
	"github.com/tidbcloud/AgentTeam/internal/config"
	"github.com/tidbcloud/AgentTeam/internal/identity/trusted"
	"github.com/tidbcloud/AgentTeam/internal/service/automation"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
	"github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
)

func TestEmbeddedAGSLocalPRMergeStartsAutomationRun(t *testing.T) {
	requireCGO(t)
	agsDSN := agsFileDatabaseDSN(t)
	gitRepoDir := t.TempDir()
	seedExistingMeshXInternalAGSIdentity(t, agsDSN, gitRepoDir)

	srv, err := New(config.ResourceRuntimeConfig{
		HTTPAddress: ":0",
		Auth:        config.ResourceRuntimeAuthConfig{Enabled: true, Header: "Authorization"},
		Database:    config.ResourceDatabaseConfig{DSN: meshxDatabaseDSN(t)},
		AGS: config.AGSConfig{
			DatabaseDSN: agsDSN,
			GitRepoDir:  gitRepoDir,
			BaseURL:     "http://127.0.0.1:3002",
		},
	}, Options{AutomationAuthorizer: automation.AllowAutomationAuthorizer{}})
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, srv.Shutdown(context.Background())) })

	internalUser := doEmbeddedAGSRequest(t, srv.handler, http.MethodGet, "/api/v3/user", agsbridge.AuthorizationHeader(), nil)
	require.Equal(t, http.StatusOK, internalUser.Code, internalUser.Body.String())
	require.Contains(t, internalUser.Body.String(), `"login":"`+agsbridge.WorkloadSubject+`"`)

	const (
		workspaceID = "ws-ags-local-merge"
		repoName    = "merge-trigger"
		repoFull    = "testuser/merge-trigger"
		featureRef  = "feature/merge-trigger"
	)
	auth := "Bearer " + jwt(map[string]any{
		"sub":                "user-1",
		"preferred_username": "testuser",
		"name":               "Test User",
		"email":              "testuser@example.com",
	})
	automationYAML := "name: Local merge trigger\non:\n  push:\n    branches: [main]\njobs:\n  build:\n    steps:\n      - run: echo local-merge\n"

	createRepoRec := doAutomationEmbeddedAGSJSONRequest(t, srv.handler, http.MethodPost, "/api/v3/user/repos", auth, map[string]any{
		"name":           repoName,
		"default_branch": "main",
		"auto_init":      true,
	})
	require.Equal(t, http.StatusCreated, createRepoRec.Code, createRepoRec.Body.String())
	var createRepoBody struct {
		ID            any    `json:"id"`
		FullName      string `json:"full_name"`
		DefaultBranch string `json:"default_branch"`
	}
	require.NoError(t, json.Unmarshal(createRepoRec.Body.Bytes(), &createRepoBody))
	require.Equal(t, repoFull, createRepoBody.FullName)
	require.Equal(t, "main", createRepoBody.DefaultBranch)
	grantor, ok := srv.ags.(agsRepositoryCollaboratorGrantor)
	require.True(t, ok, "embedded AGS should expose repository collaborator grant")
	_, err = grantor.GrantRepositoryCollaborator(context.Background(), agsserver.RepositoryCollaboratorGrantRequest{
		Owner:      "testuser",
		Repo:       repoName,
		Permission: "write",
		User: agsauth.Identity{
			Provider: "agentteam",
			Subject:  agsbridge.WorkloadSubject,
			Login:    agsbridge.WorkloadSubject,
			Name:     "MeshX Internal",
		},
	})
	require.NoError(t, err)

	automationService := automation.NewAutomationsService(automationstore.New(srv.meshxDB), func() time.Time { return time.Now().UTC() }, automation.AGSHTTPAutomationSourceResolver{
		Handler:    srv.handler,
		AuthHeader: "Authorization",
	})
	automationService.SetAuthorizer(automation.AllowAutomationAuthorizer{})
	automationService.SetRepositoryStore(repos.New(srv.meshxDB))
	_, err = automationService.SaveRepositoryAutomationFile(context.Background(), workspaceID, "user-1", automation.AutomationRepositoryAutomationSaveMutation{
		RepositoryOwner:     "testuser",
		RepositoryName:      repoName,
		DefinitionPath:      ".agentteam/push.yml",
		Branch:              "main",
		Content:             automationYAML,
		Message:             "add automation",
		AuthorizationHeader: auth,
	})
	require.NoError(t, err)
	binding, err := repos.New(srv.meshxDB).Get(context.Background(), workspaceID, repos.ResourceTypeAutomationRepository, repos.AutomationRepositoryResourceID(repoFull), repos.RepoRolePrimary)
	require.NoError(t, err)
	require.Equal(t, repoFull, binding.RepoFullName)

	mainRefRec := doEmbeddedAGSRequest(t, srv.handler, http.MethodGet, "/api/v3/repos/"+repoFull+"/git/refs/heads/main", auth, nil)
	require.Equal(t, http.StatusOK, mainRefRec.Code, mainRefRec.Body.String())
	var mainRefBody struct {
		Object struct {
			SHA string `json:"sha"`
		} `json:"object"`
	}
	require.NoError(t, json.Unmarshal(mainRefRec.Body.Bytes(), &mainRefBody))
	require.NotEmpty(t, mainRefBody.Object.SHA)

	createFeatureRec := doAutomationEmbeddedAGSJSONRequest(t, srv.handler, http.MethodPost, "/api/v3/repos/"+repoFull+"/git/refs", auth, map[string]any{
		"ref": "refs/heads/" + featureRef,
		"sha": mainRefBody.Object.SHA,
	})
	require.Equal(t, http.StatusCreated, createFeatureRec.Code, createFeatureRec.Body.String())

	addFeatureRec := doAutomationEmbeddedAGSJSONRequest(t, srv.handler, http.MethodPut, "/api/v3/repos/"+repoFull+"/contents/feature.txt", auth, map[string]any{
		"message": "feature work",
		"branch":  featureRef,
		"content": base64.StdEncoding.EncodeToString([]byte("hello merge trigger\n")),
	})
	require.Equal(t, http.StatusCreated, addFeatureRec.Code, addFeatureRec.Body.String())

	createPRRec := doAutomationEmbeddedAGSJSONRequest(t, srv.handler, http.MethodPost, "/api/v3/repos/"+repoFull+"/pulls", auth, map[string]any{
		"title": "feat: local merge trigger",
		"head":  featureRef,
		"base":  "main",
	})
	require.Equal(t, http.StatusCreated, createPRRec.Code, createPRRec.Body.String())

	mergePRRec := doAutomationEmbeddedAGSJSONRequest(t, srv.handler, http.MethodPut, "/api/v3/repos/"+repoFull+"/pulls/1/merge", auth, map[string]any{
		"merge_method": "merge",
	})
	require.Equal(t, http.StatusOK, mergePRRec.Code, mergePRRec.Body.String())
	var mergePRBody struct {
		SHA    string `json:"sha"`
		Merged bool   `json:"merged"`
	}
	require.NoError(t, json.Unmarshal(mergePRRec.Body.Bytes(), &mergePRBody))
	require.True(t, mergePRBody.Merged)
	require.NotEmpty(t, mergePRBody.SHA)

	var runs []automationstore.AutomationRun
	require.Eventually(t, func() bool {
		runs = nil
		if err := srv.meshxDB.Where("workspace_id = ?", workspaceID).Order("created_at ASC").Find(&runs).Error; err != nil {
			return false
		}
		return len(runs) == 1
	}, 3*time.Second, 50*time.Millisecond)

	run := runs[0]
	require.Equal(t, repoFull, run.RepositoryFullName)
	require.Equal(t, ".agentteam/push.yml", run.DefinitionPath)
	require.Equal(t, mergePRBody.SHA, run.GitRef)
	require.Equal(t, mergePRBody.SHA, run.CommitSHA)
	require.Equal(t, "push", run.TriggerKind)
	require.Equal(t, "repository_ref", run.TargetKind)
	require.Equal(t, repoFull+"@refs/heads/main#"+mergePRBody.SHA, run.TargetRef)
	require.Equal(t, automation.AutomationEventerPrincipalID, run.OwnerPrincipalID)
	require.Equal(t, automation.AutomationEventerPrincipalID, run.CreatedByPrincipalID)

	triggerContext := automation.ParseAutomationRunTriggerContext(run.Metadata)
	require.NotNil(t, triggerContext)
	require.Equal(t, repoFull, triggerContext.RepositoryFullName)
	require.Equal(t, "refs/heads/main", triggerContext.Ref)
	require.Equal(t, "main", triggerContext.Branch)
	require.Equal(t, mainRefBody.Object.SHA, triggerContext.BeforeSHA)
	require.Equal(t, mergePRBody.SHA, triggerContext.AfterSHA)
	require.Equal(t, "user:testuser", triggerContext.ActorRef)
	require.Equal(t, "Test User", triggerContext.ActorDisplay)
	require.Equal(t, "push", triggerContext.TriggerKind)
	require.NotEmpty(t, triggerContext.EventID)
	require.Equal(t, triggerContext.EventID, run.TriggerRef)
	require.Contains(t, run.InputJSON, `"pull_request"`)
	require.Contains(t, run.InputJSON, `"merged":true`)
}

func seedExistingMeshXInternalAGSIdentity(t *testing.T, dsn string, gitRepoDir string) {
	t.Helper()
	seedServer, err := agsserver.New(agsconfig.Config{
		DBdsn:       dsn,
		GitRepoDir:  gitRepoDir,
		BaseURL:     "http://127.0.0.1:3002",
		ListenMode:  "production",
		Environment: "production",
	}, agsserver.WithAuthenticator(ags.AgentTeamAuthenticator{Options: trusted.Options{Enabled: true, Header: "Authorization"}}))
	require.NoError(t, err)
	seeded := agsbridge.DoJSONRequest(context.Background(), seedServer.Handler(), "Authorization", agsbridge.AuthorizationHeader(), http.MethodGet, "/api/v3/user", nil)
	require.Equal(t, http.StatusOK, seeded.Code, seeded.Body.String())
	require.Contains(t, seeded.Body.String(), `"login":"`+agsbridge.WorkloadSubject+`"`)
	require.NoError(t, seedServer.Shutdown(context.Background()))
}

func doAutomationEmbeddedAGSJSONRequest(t *testing.T, handler http.Handler, method, path, auth string, body any) *httptest.ResponseRecorder {
	t.Helper()
	var payload []byte
	var err error
	if body != nil {
		payload, err = json.Marshal(body)
		require.NoError(t, err)
	}
	return doEmbeddedAGSRequest(t, handler, method, path, auth, payload)
}

func doEmbeddedAGSRequest(t *testing.T, handler http.Handler, method, path, auth string, body []byte) *httptest.ResponseRecorder {
	t.Helper()
	req := httptest.NewRequest(method, path, bytes.NewReader(body))
	if auth != "" {
		req.Header.Set("Authorization", auth)
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	rec := httptest.NewRecorder()
	handler.ServeHTTP(rec, req)
	return rec
}
