package forwarder_test

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

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

	"github.com/tidbcloud/AgentTeam/internal/forwarder"
	"github.com/tidbcloud/AgentTeam/internal/inprochttp"
	"github.com/tidbcloud/AgentTeam/internal/meshxruntime"
	"github.com/tidbcloud/AgentTeam/internal/schema"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

type integrationAuthStore struct {
	authenticated identity.AuthenticatedUser
}

func (store integrationAuthStore) AuthenticateSession(_ context.Context, _ string) (identity.AuthenticatedUser, error) {
	return store.authenticated, nil
}

type integrationWorkspaceStore struct {
	workspace workspace.Summary
}

func (store integrationWorkspaceStore) GetWorkspaceForUser(_ context.Context, _ string, _ string) (workspace.Summary, error) {
	return store.workspace, nil
}

func TestForwarderReachesEmbeddedAGSWithSQLite(t *testing.T) {
	gin.SetMode(gin.ReleaseMode)

	meshxURL, meshxClient, cleanup := startEmbeddedMeshX(t)
	defer cleanup()

	router := gin.New()
	forwarder.RegisterRoutes(router, forwarder.Dependencies{
		AuthStore: integrationAuthStore{authenticated: identity.AuthenticatedUser{
			User: model.User{
				ID:          "usr_e2e_1",
				Email:       "agentteam-e2e@example.com",
				Name:        "agentteam-e2e",
				DisplayName: "AgentTeam E2E",
			},
			Session: model.UserSession{ID: "sess_e2e_1", UserID: "usr_e2e_1"},
		}},
		BaseURL:            meshxURL,
		TrustedJWTAudience: "meshx",
		HTTPClient:         meshxClient,
	})
	agentteamServer := httptest.NewServer(router)
	defer agentteamServer.Close()

	request, err := http.NewRequest(http.MethodGet, agentteamServer.URL+"/api/v3/user", nil)
	require.NoError(t, err)
	request.Header.Set("Authorization", "Bearer client-supplied-token")

	client := agentteamServer.Client()
	client.Timeout = 10 * time.Second
	response, err := client.Do(request)
	require.NoError(t, err)
	defer response.Body.Close()
	body := new(bytes.Buffer)
	_, _ = body.ReadFrom(response.Body)

	require.Equal(t, http.StatusOK, response.StatusCode, body.String())
	require.Contains(t, body.String(), `"email":"agentteam-e2e@example.com"`)
	require.Contains(t, body.String(), `"login":"agentteam-e2e"`)
}

func TestForwarderReachesEmbeddedAutomationRunLedger(t *testing.T) {
	gin.SetMode(gin.ReleaseMode)

	meshxURL, meshxClient, cleanup := startEmbeddedMeshX(t)
	defer cleanup()

	router := gin.New()
	forwarder.RegisterRoutes(router, forwarder.Dependencies{
		AuthStore: integrationAuthStore{authenticated: identity.AuthenticatedUser{
			User: model.User{
				ID:          "usr_automation_1",
				Email:       "automation@example.com",
				Name:        "automation-user",
				DisplayName: "Automation User",
			},
			Session: model.UserSession{ID: "sess_automation_1", UserID: "usr_automation_1"},
		}},
		WorkspaceStore:     integrationWorkspaceStore{workspace: workspace.Summary{WorkspaceID: "ws-forwarder", WorkspaceRole: "member"}},
		BaseURL:            meshxURL,
		MeshXEnabled:       true,
		TrustedJWTAudience: "meshx",
		HTTPClient:         meshxClient,
	})
	agentteamServer := httptest.NewServer(router)
	defer agentteamServer.Close()

	client := agentteamServer.Client()
	client.Timeout = 10 * time.Second

	createRepoBody := bytes.NewBufferString(`{"name":"forwarder-automations"}`)
	createRepoRequest, err := http.NewRequest(http.MethodPost, agentteamServer.URL+"/api/v3/user/repos", createRepoBody)
	require.NoError(t, err)
	createRepoRequest.Header.Set("Authorization", "Bearer client-supplied-token")
	createRepoRequest.Header.Set("Content-Type", "application/json")
	createRepoResponse, err := client.Do(createRepoRequest)
	require.NoError(t, err)
	defer createRepoResponse.Body.Close()
	createRepoResponseBody := new(bytes.Buffer)
	_, _ = createRepoResponseBody.ReadFrom(createRepoResponse.Body)
	require.Equal(t, http.StatusCreated, createRepoResponse.StatusCode, createRepoResponseBody.String())

	automationYAML := "name: Forwarded automation\non:\n  manual: {}\njobs:\n  plan:\n    runs-on: ubuntu-latest\n    steps:\n      - run: echo forwarded\n"
	writeAutomationBody, err := json.Marshal(map[string]string{
		"message": "add automation",
		"content": base64.StdEncoding.EncodeToString([]byte(automationYAML)),
	})
	require.NoError(t, err)
	writeAutomationRequest, err := http.NewRequest(http.MethodPut, agentteamServer.URL+"/api/v3/repos/automation-user/forwarder-automations/contents/.agentteam/forwarded.yml", bytes.NewBuffer(writeAutomationBody))
	require.NoError(t, err)
	writeAutomationRequest.Header.Set("Authorization", "Bearer client-supplied-token")
	writeAutomationRequest.Header.Set("Content-Type", "application/json")
	writeAutomationResponse, err := client.Do(writeAutomationRequest)
	require.NoError(t, err)
	defer writeAutomationResponse.Body.Close()
	writeAutomationResponseBody := new(bytes.Buffer)
	_, _ = writeAutomationResponseBody.ReadFrom(writeAutomationResponse.Body)
	require.Equal(t, http.StatusCreated, writeAutomationResponse.StatusCode, writeAutomationResponseBody.String())

	body := bytes.NewBufferString(`{"ref":"refs/heads/main","definitionPath":".agentteam/forwarded.yml","triggerKind":"task","triggerRef":"task:#agentteam-in-agentteam:1602"}`)
	request, err := http.NewRequest(http.MethodPost, agentteamServer.URL+"/api/v1/workspaces/ws-forwarder/automations/repositories/automation-user/forwarder-automations/runs", body)
	require.NoError(t, err)
	request.Header.Set("Authorization", "Bearer client-supplied-token")
	request.Header.Set("Content-Type", "application/json")
	request.Header.Set("X-Workspace-Id", "ws-forwarder")

	response, err := client.Do(request)
	require.NoError(t, err)
	defer response.Body.Close()
	responseBody := new(bytes.Buffer)
	_, _ = responseBody.ReadFrom(response.Body)

	require.Equal(t, http.StatusCreated, response.StatusCode, responseBody.String())
	require.Contains(t, responseBody.String(), `"workspaceId":"ws-forwarder"`)
	require.Contains(t, responseBody.String(), `"createdByPrincipalId":"usr_automation_1"`)
	require.Contains(t, responseBody.String(), `"repositoryFullName":"automation-user/forwarder-automations"`)
	require.Contains(t, responseBody.String(), `"definitionPath":".agentteam/forwarded.yml"`)
	require.Contains(t, responseBody.String(), `"status":"running"`)
}

func TestForwarderCreatesOrgRepositoryThroughEmbeddedAGS(t *testing.T) {
	gin.SetMode(gin.ReleaseMode)

	meshxURL, meshxClient, cleanup := startEmbeddedMeshX(t)
	defer cleanup()

	router := gin.New()
	forwarder.RegisterRoutes(router, forwarder.Dependencies{
		AuthStore: integrationAuthStore{authenticated: identity.AuthenticatedUser{
			User: model.User{
				ID:          "usr_org_repo_1",
				Email:       "org-repo@example.com",
				Name:        "org-repo-user",
				DisplayName: "Org Repo User",
			},
			Session: model.UserSession{ID: "sess_org_repo_1", UserID: "usr_org_repo_1"},
		}},
		BaseURL:            meshxURL,
		TrustedJWTAudience: "meshx",
		HTTPClient:         meshxClient,
	})
	agentteamServer := httptest.NewServer(router)
	defer agentteamServer.Close()

	client := agentteamServer.Client()
	client.Timeout = 10 * time.Second

	createOrgRequest, err := http.NewRequest(http.MethodPost, agentteamServer.URL+"/api/ext/v1/user/orgs", bytes.NewBufferString(`{"login":"agentteam","name":"agentteam"}`))
	require.NoError(t, err)
	createOrgRequest.Header.Set("Authorization", "Bearer client-supplied-token")
	createOrgRequest.Header.Set("Content-Type", "application/json")
	createOrgResponse, err := client.Do(createOrgRequest)
	require.NoError(t, err)
	defer createOrgResponse.Body.Close()
	createOrgBody := new(bytes.Buffer)
	_, _ = createOrgBody.ReadFrom(createOrgResponse.Body)
	require.Equal(t, http.StatusCreated, createOrgResponse.StatusCode, createOrgBody.String())

	createRepoRequest, err := http.NewRequest(http.MethodPost, agentteamServer.URL+"/api/v3/orgs/agentteam/repos", bytes.NewBufferString(`{"name":"org-forwarded-repo"}`))
	require.NoError(t, err)
	createRepoRequest.Header.Set("Authorization", "Bearer client-supplied-token")
	createRepoRequest.Header.Set("Content-Type", "application/json")
	createRepoResponse, err := client.Do(createRepoRequest)
	require.NoError(t, err)
	defer createRepoResponse.Body.Close()
	createRepoBody := new(bytes.Buffer)
	_, _ = createRepoBody.ReadFrom(createRepoResponse.Body)
	require.Equal(t, http.StatusCreated, createRepoResponse.StatusCode, createRepoBody.String())
	require.Contains(t, createRepoBody.String(), `"full_name":"agentteam/org-forwarded-repo"`)
}

func TestForwarderEmbeddedAGSIdentityDoesNotCollapseDistinctCJKUsers(t *testing.T) {
	gin.SetMode(gin.ReleaseMode)

	meshxURL, meshxClient, cleanup := startEmbeddedMeshX(t)
	defer cleanup()

	newAgentTeamServer := func(user model.User, session model.UserSession) *httptest.Server {
		router := gin.New()
		forwarder.RegisterRoutes(router, forwarder.Dependencies{
			AuthStore: integrationAuthStore{authenticated: identity.AuthenticatedUser{
				User:    user,
				Session: session,
			}},
			BaseURL:            meshxURL,
			TrustedJWTAudience: "meshx",
			HTTPClient:         meshxClient,
		})
		return httptest.NewServer(router)
	}

	first := newAgentTeamServer(
		model.User{
			ID:          "usr_cjk_1",
			Email:       "first-cjk@example.com",
			Name:        "柏佳辰",
			DisplayName: "柏佳辰",
		},
		model.UserSession{ID: "sess_cjk_1", UserID: "usr_cjk_1"},
	)
	defer first.Close()
	second := newAgentTeamServer(
		model.User{
			ID:          "usr_cjk_2",
			Email:       "second-cjk@example.com",
			Name:        "李雷",
			DisplayName: "李雷",
		},
		model.UserSession{ID: "sess_cjk_2", UserID: "usr_cjk_2"},
	)
	defer second.Close()

	requestAs := func(serverURL string) (int, string) {
		req, err := http.NewRequest(http.MethodGet, serverURL+"/api/v3/user", nil)
		require.NoError(t, err)
		req.Header.Set("Authorization", "Bearer client-supplied-token")
		client := first.Client()
		client.Timeout = 10 * time.Second
		resp, err := client.Do(req)
		require.NoError(t, err)
		defer resp.Body.Close()
		body := new(bytes.Buffer)
		_, _ = body.ReadFrom(resp.Body)
		return resp.StatusCode, body.String()
	}

	firstStatus, firstBody := requestAs(first.URL)
	require.Equal(t, http.StatusOK, firstStatus, firstBody)
	secondStatus, secondBody := requestAs(second.URL)
	require.Equal(t, http.StatusOK, secondStatus, secondBody)
	require.Contains(t, secondBody, `"email":"second-cjk@example.com"`)
}

func startEmbeddedMeshX(t *testing.T) (string, *http.Client, func()) {
	t.Helper()
	tempDir := t.TempDir()
	t.Setenv("AGENTTEAM_DATABASE_DSN", "")
	t.Setenv("MESHX_DATABASE_DSN", "file:"+filepath.Join(tempDir, "meshx.db")+"?cache=shared&_foreign_keys=on")
	t.Setenv("MESHX_AUTH_ENABLED", "true")
	t.Setenv("MESHX_AUTH_ALLOW_MOCK", "false")
	t.Setenv("MESHX_AUTH_TRUSTED_JWT_CLAIMS", "true")
	t.Setenv("MESHX_AUTH_TRUSTED_JWT_CLAIMS_PROTECTED_LISTENER", "true")
	t.Setenv("MESHX_AGS_DATABASE_DSN", "file:"+filepath.Join(tempDir, "ags.db")+"?_foreign_keys=on")
	t.Setenv("MESHX_AGS_GIT_REPO_DIR", filepath.Join(tempDir, "repos"))
	t.Setenv("AGENTTEAM_PUBLIC_BASE_URL", "http://embedded.meshx")
	t.Setenv("MESHX_AGS_ALLOW_ANY_TOKEN", "false")
	t.Setenv("MESHX_AUTOMATION_SERVER_RUNNER_ENABLED", "false")

	require.NoError(t, schema.MigrateFromEnv(t.Context()))
	runtime, err := meshxruntime.NewFromEnv()
	require.NoError(t, err)
	require.NoError(t, runtime.StartWorkers(meshxruntime.WorkerOwnership{StartGlobalWorkers: true}))
	readyCtx, cancelReady := context.WithTimeout(t.Context(), 10*time.Second)
	require.NoError(t, runtime.Ready(readyCtx))
	cancelReady()

	client := &http.Client{
		Timeout:   10 * time.Second,
		Transport: inprochttp.RoundTripper{Handler: runtime.Handler()},
	}
	return "http://embedded.meshx", client, func() {
		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		require.NoError(t, runtime.Shutdown(shutdownCtx))
	}
}
