package mailapi

import (
	"context"
	"net/http"
	"strings"
	"testing"

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

	"github.com/tidbcloud/AgentTeam/internal/api/testkit/httputil"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

func TestEmailConnectionRoutesExposeOnlySetupManagement(t *testing.T) {
	service := &fakeConnectionService{
		installResult: InstallResult{
			InstallationID: "appinst_email",
			ConnectionID:   "conn_email",
			ExtensionName:  "work-mail",
			DisplayName:    "Work Mail",
			SenderAddress:  "CaseSensitive@example.com",
			Capabilities:   []string{"imap:IMAP4REV1"},
		},
		reconnectResult: ReconnectResult{
			ConnectionID:  "conn_email",
			SenderAddress: "CaseSensitive@example.com",
			Capabilities:  []string{"imap:IMAP4REV1"},
		},
	}
	router := connectionRouter(service)
	install := httputil.DoJSON(
		t,
		router,
		http.MethodPost,
		"/api/v1/workspaces/wsp_1/apps/email/installations",
		validInstallJSON(),
	)
	require.Equal(t, http.StatusCreated, install.Code)
	require.Contains(t, install.Body.String(), `"extensionName":"work-mail"`)
	require.NotContains(t, install.Body.String(), "password")
	require.NotContains(t, install.Body.String(), "secret")
	require.Equal(t, "no-store", install.Header().Get("Cache-Control"))
	require.Equal(t, "CaseSensitive@EXAMPLE.COM", service.install.SenderAddress)

	reconnectBody := validInstallJSON()
	delete(reconnectBody, "extensionName")
	delete(reconnectBody, "displayName")
	delete(reconnectBody, "senderAddress")
	reconnect := httputil.DoJSON(
		t,
		router,
		http.MethodPut,
		"/api/v1/workspaces/wsp_1/app-installations/appinst_email/connection",
		reconnectBody,
	)
	require.Equal(t, http.StatusOK, reconnect.Code)
	require.Equal(t, "appinst_email", service.installationID)

	disconnect := httputil.DoJSON(
		t,
		router,
		http.MethodDelete,
		"/api/v1/workspaces/wsp_1/app-installations/appinst_email/connections/conn_email",
		nil,
	)
	require.Equal(t, http.StatusNoContent, disconnect.Code)
	require.Equal(t, "conn_email", service.connectionID)

	counts := httputil.RouteCounts(router)
	require.Equal(t, 1, counts["POST /api/v1/workspaces/:workspaceId/apps/email/installations"])
	require.Equal(t, 1, counts["PUT /api/v1/workspaces/:workspaceId/app-installations/:installationId/connection"])
	require.Equal(t, 1, counts["DELETE /api/v1/workspaces/:workspaceId/app-installations/:installationId/connections/:connectionId"])
	for route := range counts {
		require.NotContains(t, route, "oauth")
		require.NotContains(t, route, "/messages")
		require.NotContains(t, route, "/drafts")
	}
}

func TestEmailConnectionRoutesStrictlyRejectIdentityOverrides(t *testing.T) {
	service := &fakeConnectionService{}
	router := connectionRouter(service)
	body := validInstallJSON()
	body["provider"] = "forbidden"
	response := httputil.DoJSON(
		t,
		router,
		http.MethodPost,
		"/api/v1/workspaces/wsp_1/apps/email/installations",
		body,
	)
	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Zero(t, service.installCalls)

	reconnectBody := validInstallJSON()
	delete(reconnectBody, "extensionName")
	delete(reconnectBody, "displayName")
	response = httputil.DoJSON(
		t,
		router,
		http.MethodPut,
		"/api/v1/workspaces/wsp_1/app-installations/appinst_email/connection",
		reconnectBody,
	)
	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Zero(t, service.reconnectCalls)

	invalidPort := validInstallJSON()
	invalidPort["smtp"].(map[string]any)["port"] = 25
	response = httputil.DoJSON(
		t,
		router,
		http.MethodPost,
		"/api/v1/workspaces/wsp_1/apps/email/installations",
		invalidPort,
	)
	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Zero(t, service.installCalls)
}

func TestEmailConnectionRoutesBoundSetupBodies(t *testing.T) {
	service := &fakeConnectionService{}
	router := connectionRouter(service)
	response := httputil.DoRaw(
		t,
		router,
		http.MethodPost,
		"/api/v1/workspaces/wsp_1/apps/email/installations",
		http.Header{
			"Authorization":  []string{"Bearer token"},
			"X-Workspace-Id": []string{"wsp_1"},
			"Content-Type":   []string{"application/json"},
		},
		[]byte(`{"extensionName":"`+
			strings.Repeat("x", setupBodyLimit)+`"}`),
	)
	require.Equal(t, http.StatusRequestEntityTooLarge, response.Code)
	require.Zero(t, service.installCalls)
}

func connectionRouter(service ConnectionService) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	RegisterWorkspaceRoutes(router, Dependencies{
		SessionAuth:    connectionFakeSessionAuth{},
		WorkspaceStore: connectionFakeWorkspaceResolver{},
		Service:        service,
	})
	return router
}

type connectionFakeSessionAuth struct{}

func (connectionFakeSessionAuth) AuthenticateSession(
	context.Context,
	string,
) (identity.AuthenticatedUser, error) {
	return identity.AuthenticatedUser{
		User: identity.User{ID: "usr_1"},
	}, nil
}

type connectionFakeWorkspaceResolver struct{}

func (connectionFakeWorkspaceResolver) GetWorkspaceForUser(
	context.Context,
	string,
	string,
) (workspace.Summary, error) {
	return workspace.Summary{WorkspaceID: "wsp_1"}, nil
}

type fakeConnectionService struct {
	installResult   InstallResult
	reconnectResult ReconnectResult
	err             error
	installCalls    int
	reconnectCalls  int
	install         mail.InstallRequest
	installationID  string
	connectionID    string
}

func (fake *fakeConnectionService) Install(
	_ context.Context,
	_ string,
	_ string,
	request mail.InstallRequest,
) (InstallResult, error) {
	fake.installCalls++
	fake.install = request
	return fake.installResult, fake.err
}

func (fake *fakeConnectionService) Reconnect(
	_ context.Context,
	_ string,
	_ string,
	installationID string,
	_ mail.ReconnectRequest,
) (ReconnectResult, error) {
	fake.reconnectCalls++
	fake.installationID = installationID
	return fake.reconnectResult, fake.err
}

func (fake *fakeConnectionService) Disconnect(
	_ context.Context,
	_ string,
	_ string,
	installationID string,
	connectionID string,
) error {
	fake.installationID = installationID
	fake.connectionID = connectionID
	return fake.err
}

func validInstallJSON() map[string]any {
	return map[string]any{
		"extensionName": "work-mail",
		"displayName":   "Work Mail",
		"senderAddress": "CaseSensitive@EXAMPLE.COM",
		"imap": map[string]any{
			"host":    "imap.example.com",
			"port":    993,
			"tlsMode": "implicit",
			"auth": map[string]any{
				"username": "imap-user",
				"password": "imap-secret",
			},
		},
		"smtpSameAsImap": true,
		"smtp": map[string]any{
			"host":    "smtp.example.com",
			"port":    587,
			"tlsMode": "starttls",
		},
	}
}
