//go:build e2e

package testkit

import (
	"fmt"
	"testing"

	"github.com/tidbcloud/AgentTeam/e2e/assertions"
	"github.com/tidbcloud/AgentTeam/e2e/client"
)

// Session is an authenticated API session for scenario tests.
type Session struct {
	AccessToken string
	UserID      string
	API         *client.Client
}

// PeerSession is a second user in the same workspace as the host.
type PeerSession struct {
	Session
	UserID      string
	WorkspaceID string
}

// RequirePrimaryUser returns the default scenario actor (user A).
func RequirePrimaryUser(t *testing.T, env Environment) Session {
	t.Helper()
	return requireUser(t, env, LoadConfig(t).UserA, "test-user", env.API)
}

// RequireSession is an alias for RequirePrimaryUser.
func RequireSession(t *testing.T, env Environment) Session {
	t.Helper()
	return RequirePrimaryUser(t, env)
}

// RequireRegisteredUser returns a fresh registered user in embedded mode, or user A in remote mode.
func RequireRegisteredUser(t *testing.T, env Environment, label string) client.AuthSession {
	t.Helper()
	cfg := LoadConfig(t)
	if cfg.UserA.AccessToken != "" {
		session := requireUser(t, env, cfg.UserA, label, env.API)
		return client.AuthSession{
			AccessToken: session.AccessToken,
			User: client.User{
				ID: session.UserID,
			},
		}
	}
	if cfg.Profile == ProfileRemote {
		t.Fatalf("remote profile requires AGENTTEAM_E2E_ACCESS_TOKEN (user A); cannot register new user %q", label)
	}
	return registerEmbeddedUser(t, env, label)
}

// RequirePeerUser returns a second workspace member (embedded invite flow or remote user B).
func RequirePeerUser(t *testing.T, env Environment, host Session, workspace client.Workspace) PeerSession {
	t.Helper()
	return RequirePeerInWorkspace(t, env, host, workspace, "peer")
}

// RequirePeerInWorkspace provisions or loads the peer user for multi-actor scenarios.
func RequirePeerInWorkspace(t *testing.T, env Environment, host Session, workspace client.Workspace, peerLabel string) PeerSession {
	t.Helper()
	cfg := LoadConfig(t)
	if cfg.Profile == ProfileRemote || cfg.UserB.AccessToken != "" {
		return requireRemotePeer(t, env, cfg.UserB, workspace)
	}
	return requireEmbeddedPeer(t, env, host, workspace, peerLabel)
}

func requireUser(t *testing.T, env Environment, creds UserCredentials, label string, base *client.Client) Session {
	t.Helper()
	if creds.AccessToken != "" {
		api := base.WithToken(creds.AccessToken)
		userID := creds.UserID
		if userID == "" {
			userID = requireUserIDFromAPI(t, api)
		}
		t.Logf("using preset user %q: userId=%s", label, userID)
		return Session{
			AccessToken: creds.AccessToken,
			UserID:      userID,
			API:         api,
		}
	}
	if env.Profile == ProfileRemote {
		t.Fatalf("remote profile requires an access token for user %q", label)
	}
	registered := registerEmbeddedUser(t, env, label)
	return Session{
		AccessToken: registered.AccessToken,
		UserID:      registered.User.ID,
		API:         env.API.WithToken(registered.AccessToken),
	}
}

func registerEmbeddedUser(t *testing.T, env Environment, label string) client.AuthSession {
	t.Helper()

	ctx, cancel := APIContext(t)
	defer cancel()

	userKey := CaseName(label)
	email := fmt.Sprintf("%s@%s", userKey, DefaultEmailDomain)
	registered, status, raw, err := env.API.Register(ctx, DefaultEmbeddedUserDisplayPrefix+" "+label, email, EmbeddedRegisterPassword())
	if err != nil {
		t.Fatalf("register user %s: %v", label, err)
	}
	assertions.RequireStatus(t, status, DefaultHTTPStatusOK, raw)
	if registered.AccessToken == "" || registered.User.ID == "" {
		t.Fatalf("incomplete registration response for %s", label)
	}
	t.Logf("registered embedded user %q: email=%s userId=%s", label, email, registered.User.ID)
	return registered
}

func requireUserIDFromAPI(t *testing.T, api *client.Client) string {
	t.Helper()

	ctx, cancel := APIContext(t)
	defer cancel()

	profile, status, raw, err := api.GetMe(ctx)
	if err != nil {
		t.Fatalf("resolve user id via /api/v1/me: %v", err)
	}
	assertions.RequireStatus(t, status, DefaultHTTPStatusOK, raw)
	if profile.ID == "" {
		t.Fatalf("empty user id from /api/v1/me")
	}
	return profile.ID
}

func requireRemotePeer(t *testing.T, env Environment, creds UserCredentials, workspace client.Workspace) PeerSession {
	t.Helper()
	if creds.AccessToken == "" {
		t.Fatalf("remote peer requires AGENTTEAM_E2E_USER_B_ACCESS_TOKEN or accounts file userB.accessToken")
	}
	userID := creds.UserID
	if userID == "" {
		t.Fatalf("remote peer requires AGENTTEAM_E2E_USER_B_USER_ID or accounts file userB.userId")
	}
	t.Logf("using preset peer user: userId=%s workspaceId=%s", userID, workspace.ID)
	return PeerSession{
		Session: Session{
			AccessToken: creds.AccessToken,
			UserID:      userID,
			API:         env.API.WithToken(creds.AccessToken).WithWorkspace(workspace.ID),
		},
		UserID:      userID,
		WorkspaceID: workspace.ID,
	}
}

func requireEmbeddedPeer(t *testing.T, env Environment, host Session, workspace client.Workspace, peerLabel string) PeerSession {
	t.Helper()

	ctx, cancel := PeerFlowContext(t)
	defer cancel()

	peerKey := CaseName(peerLabel)
	peerEmail := fmt.Sprintf("%s@%s", peerKey, DefaultEmailDomain)
	peerMobile := InviteMobileForEmail(peerEmail)

	invite, status, raw, err := host.API.CreateWorkspaceMobileInvite(ctx, workspace.ID, peerMobile)
	if err != nil {
		t.Fatalf("create workspace mobile invite: %v", err)
	}
	assertions.RequireStatus(t, status, DefaultHTTPStatusOK, raw)
	if invite.InviteID == "" {
		t.Fatalf("empty invite id in create invite response")
	}
	t.Logf("invited peer via mobile: email=%s inviteId=%s", peerEmail, invite.InviteID)

	registered := registerEmbeddedUser(t, env, peerLabel)
	peerAPI := env.API.WithToken(registered.AccessToken).WithWorkspace(workspace.ID)

	status, raw, err = peerAPI.RequestMobileVerification(ctx, peerMobile)
	if err != nil {
		t.Fatalf("request peer mobile verification: %v", err)
	}
	assertions.RequireStatus(t, status, DefaultHTTPStatusOK, raw)

	code, err := waitForMobileVerificationCode(ctx, env.BaseURL, peerMobile)
	if err != nil {
		t.Fatalf("wait for peer mobile verification code: %v", err)
	}

	status, raw, err = peerAPI.VerifyMobile(ctx, code)
	if err != nil {
		t.Fatalf("verify peer mobile: %v", err)
	}
	assertions.RequireStatus(t, status, DefaultHTTPStatusOK, raw)

	_, status, raw, err = peerAPI.AcceptPendingWorkspaceInvite(ctx, invite.InviteID)
	if err != nil {
		t.Fatalf("accept workspace invite: %v", err)
	}
	assertions.RequireStatus(t, status, DefaultHTTPStatusOK, raw)
	t.Logf("peer joined workspace: userId=%s workspaceId=%s", registered.User.ID, workspace.ID)

	return PeerSession{
		Session: Session{
			AccessToken: registered.AccessToken,
			UserID:      registered.User.ID,
			API:         peerAPI,
		},
		UserID:      registered.User.ID,
		WorkspaceID: workspace.ID,
	}
}

// RequireAccessToken returns the primary user's bearer token.
func RequireAccessToken(t *testing.T, env Environment) string {
	t.Helper()
	return RequirePrimaryUser(t, env).AccessToken
}

// SkipIfRemote skips the test when running against a fixed remote deployment.
func SkipIfRemote(t *testing.T, reason string) {
	t.Helper()
	if LoadConfig(t).Profile == ProfileRemote {
		t.Skip(reason)
	}
}

// SkipUnlessEmbedded skips unless the embedded profile is active.
func SkipUnlessEmbedded(t *testing.T, reason string) {
	t.Helper()
	cfg := LoadConfig(t)
	if cfg.Profile != ProfileEmbedded || cfg.UserA.AccessToken != "" {
		t.Skip(reason)
	}
}
