package workspace_test

import (
	"net/http"
	"net/http/httptest"
	"testing"

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

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	. "github.com/tidbcloud/AgentTeam/internal/api/testkit"
)

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

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()

	var auth dto.AuthTokensResponse
	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/auth/register", map[string]any{
		"email":    "miniapp-owner@example.com",
		"password": TestUserPassword,
		"name":     "miniapp-owner",
	}, nil, &auth)
	require.Equal(t, http.StatusOK, response.StatusCode)

	var workspaces []dto.WorkspaceResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces", nil, AuthHeaders(auth.AccessToken), &workspaces)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, workspaces)
	workspaceID := workspaces[0].ID

	var issueResponse dto.MiniappPairingIssueResponse
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/miniapp-pairing-codes", map[string]any{
		"serverUrl": server.URL,
	}, AuthHeaders(auth.AccessToken), &issueResponse)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, issueResponse.Code)
	require.NotEmpty(t, issueResponse.ExpiresAt)

	var exchangeResponse dto.MiniappPairingExchangeResponse
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/miniapp-pairing-exchanges", map[string]any{
		"code":           issueResponse.Code,
		"installationId": "ins_owner_phone",
		"deviceName":     "Alice's iPhone",
		"devicePlatform": "ios",
	}, nil, &exchangeResponse)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, exchangeResponse.AccessToken)
	require.NotEmpty(t, exchangeResponse.RefreshToken)
	require.Equal(t, auth.User.ID, exchangeResponse.User.ID)
	require.Equal(t, workspaceID, exchangeResponse.WorkspaceID)
	require.Equal(t, workspaces[0].Name, exchangeResponse.WorkspaceName)
	require.Equal(t, server.URL, exchangeResponse.ServerURL)

	var apiErr struct {
		Error string `json:"error"`
	}
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/miniapp-pairing-exchanges", map[string]any{
		"code":           issueResponse.Code,
		"installationId": "ins_owner_phone",
	}, nil, &apiErr)
	require.Equal(t, http.StatusGone, response.StatusCode)
}

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

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()

	var auth dto.AuthTokensResponse
	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/auth/register", map[string]any{
		"email":    "miniapp-validation@example.com",
		"password": TestUserPassword,
		"name":     "miniapp-validation",
	}, nil, &auth)
	require.Equal(t, http.StatusOK, response.StatusCode)

	var workspaces []dto.WorkspaceResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces", nil, AuthHeaders(auth.AccessToken), &workspaces)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, workspaces)
	workspaceID := workspaces[0].ID

	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/miniapp-pairing-codes", map[string]any{
		"serverUrl": "",
	}, AuthHeaders(auth.AccessToken), nil)
	require.Equal(t, http.StatusBadRequest, response.StatusCode)
}

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

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()

	var apiErr struct {
		Error string `json:"error"`
	}
	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/miniapp-pairing-exchanges", map[string]any{
		"code":           "mpc_does_not_exist",
		"installationId": "ins_unknown",
	}, nil, &apiErr)
	require.Equal(t, http.StatusGone, response.StatusCode)
}

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

	runtime := StartRuntime(t)

	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()

	response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/wsp_unknown/miniapp-pairing-codes", map[string]any{
		"serverUrl": server.URL,
	}, nil, nil)
	require.Equal(t, http.StatusUnauthorized, response.StatusCode)
}

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

	runtime := StartRuntime(t)
	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()
	client := server.Client()

	register := func(email string, name string) dto.AuthTokensResponse {
		var tokens dto.AuthTokensResponse
		response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/auth/register", map[string]any{
			"email": email, "password": TestUserPassword, "name": name,
		}, nil, &tokens)
		require.Equal(t, http.StatusOK, response.StatusCode)
		return tokens
	}
	owner := register("mobile-session-owner@example.com", "mobile-session-owner")
	other := register("mobile-session-other@example.com", "mobile-session-other")

	var workspaces []dto.WorkspaceResponse
	response := DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/workspaces", nil, AuthHeaders(owner.AccessToken), &workspaces)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, workspaces)
	workspaceID := workspaces[0].ID

	pair := func() dto.MiniappPairingExchangeResponse {
		var issued dto.MiniappPairingIssueResponse
		response := DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/workspaces/"+workspaceID+"/miniapp-pairing-codes", map[string]any{
			"serverUrl": server.URL,
		}, AuthHeaders(owner.AccessToken), &issued)
		require.Equal(t, http.StatusOK, response.StatusCode)

		var exchanged dto.MiniappPairingExchangeResponse
		response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/miniapp-pairing-exchanges", map[string]any{
			"code":           issued.Code,
			"installationId": "ins_same_phone",
			"deviceName":     "Owner phone",
			"devicePlatform": "android",
		}, nil, &exchanged)
		require.Equal(t, http.StatusOK, response.StatusCode)
		return exchanged
	}

	first := pair()
	second := pair()

	// Pairing again from the same installation immediately replaces its old
	// family, so overwritten local credentials do not remain usable.
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/me", nil, AuthHeaders(first.AccessToken), nil)
	require.Equal(t, http.StatusUnauthorized, response.StatusCode)
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/auth/refresh", map[string]any{
		"refreshToken": first.RefreshToken,
	}, nil, nil)
	require.Equal(t, http.StatusUnauthorized, response.StatusCode)

	// Signing out on the phone revokes the whole family before the client
	// removes its local credentials.
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/auth/logout", map[string]any{
		"refreshToken": second.RefreshToken,
	}, AuthHeaders(second.AccessToken), nil)
	require.Equal(t, http.StatusOK, response.StatusCode)
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/me", nil, AuthHeaders(second.AccessToken), nil)
	require.Equal(t, http.StatusUnauthorized, response.StatusCode)
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/auth/refresh", map[string]any{
		"refreshToken": second.RefreshToken,
	}, nil, nil)
	require.Equal(t, http.StatusUnauthorized, response.StatusCode)

	third := pair()

	var sessions dto.MobileSessionListResponse
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/me/mobile-sessions", nil, AuthHeaders(owner.AccessToken), &sessions)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.Len(t, sessions.Items, 1)
	require.Equal(t, "Owner phone", sessions.Items[0].DeviceName)
	require.Equal(t, "android", sessions.Items[0].DevicePlatform)

	var refreshed dto.TokenRefreshResponse
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/auth/refresh", map[string]any{
		"refreshToken": third.RefreshToken,
	}, nil, &refreshed)
	require.Equal(t, http.StatusOK, response.StatusCode)

	// A different account cannot enumerate ownership through revocation and
	// cannot affect the owner's mobile family.
	response = DoJSON(t, client, http.MethodDelete, server.URL+"/api/v1/me/mobile-sessions/"+sessions.Items[0].ID, nil, AuthHeaders(other.AccessToken), nil)
	require.Equal(t, http.StatusNotFound, response.StatusCode)
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/me", nil, AuthHeaders(refreshed.AccessToken), nil)
	require.Equal(t, http.StatusOK, response.StatusCode)

	response = DoJSON(t, client, http.MethodDelete, server.URL+"/api/v1/me/mobile-sessions/"+sessions.Items[0].ID, nil, AuthHeaders(owner.AccessToken), nil)
	require.Equal(t, http.StatusNoContent, response.StatusCode)
	response = DoJSON(t, client, http.MethodGet, server.URL+"/api/v1/me", nil, AuthHeaders(refreshed.AccessToken), nil)
	require.Equal(t, http.StatusUnauthorized, response.StatusCode)
	response = DoJSON(t, client, http.MethodPost, server.URL+"/api/v1/auth/refresh", map[string]any{
		"refreshToken": refreshed.RefreshToken,
	}, nil, nil)
	require.Equal(t, http.StatusUnauthorized, response.StatusCode)
}
