package mailapi

import (
	"context"
	"errors"
	"net/http"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

const setupBodyLimit = 16 * 1024

type ConnectionService interface {
	Install(
		context.Context,
		string,
		string,
		mail.InstallRequest,
	) (InstallResult, error)
	Reconnect(
		context.Context,
		string,
		string,
		string,
		mail.ReconnectRequest,
	) (ReconnectResult, error)
	Disconnect(
		context.Context,
		string,
		string,
		string,
		string,
	) error
}

type WorkspaceAppsRealtime interface {
	EmitWorkspaceAppsUpdated(workspaceID string)
}

type Dependencies struct {
	SessionAuth               api.SessionAuthenticator
	WorkspaceStore            api.WorkspaceResolver
	Service                   ConnectionService
	Realtime                  WorkspaceAppsRealtime
	EmailVerificationRequired bool
}

func RegisterWorkspaceRoutes(
	router gin.IRouter,
	dependencies Dependencies,
) {
	if router == nil || dependencies.Service == nil {
		return
	}
	handler := &Handler{
		service:  dependencies.Service,
		realtime: dependencies.Realtime,
	}
	authenticated := router.Group(
		"/api/v1",
		api.RequireAuthenticatedUser(dependencies.SessionAuth),
		api.RequireVerifiedIdentity(
			dependencies.EmailVerificationRequired,
		),
	)
	workspaceScoped := authenticated.Group(
		"/workspaces/:workspaceId",
		api.RequireRouteWorkspace(dependencies.WorkspaceStore),
	)
	workspaceScoped.POST(
		"/apps/email/installations",
		limitSetupBody(),
		handleWorkspaceJSON(handler.Install),
	)
	workspaceScoped.PUT(
		"/app-installations/:installationId/connection",
		limitSetupBody(),
		handleWorkspaceJSON(handler.Reconnect),
	)
	workspaceScoped.DELETE(
		"/app-installations/:installationId/connections/:connectionId",
		handleWorkspace(handler.Disconnect),
	)
}

func handleWorkspace(
	handler func(api.WorkspaceScopedRequest) (api.Response, error),
) gin.HandlerFunc {
	return api.HandleWorkspaceScoped(
		handler,
		api.WithErrorClassifier(classifyConnectionError),
	)
}

func handleWorkspaceJSON[T any](
	handler func(api.WorkspaceScopedJSON[T]) (api.Response, error),
) gin.HandlerFunc {
	return api.HandleWorkspaceScopedJSON(
		handler,
		api.WithStrictJSON(),
		api.WithErrorClassifier(classifyConnectionError),
	)
}

func limitSetupBody() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		ctx.Request.Body = http.MaxBytesReader(
			ctx.Writer,
			ctx.Request.Body,
			setupBodyLimit,
		)
		ctx.Next()
	}
}

func classifyConnectionError(err error) (api.ErrorResponse, bool) {
	switch {
	case errors.Is(err, storeerr.ErrBadRequest):
		return api.ErrorResponse{
			Status:  http.StatusBadRequest,
			Message: err.Error(),
			Code:    "email_invalid_connection",
		}, true
	case errors.Is(err, storeerr.ErrForbidden),
		errors.Is(err, storeerr.ErrUnauthorized):
		return api.ErrorResponse{
			Status:  http.StatusForbidden,
			Message: err.Error(),
			Code:    "email_connection_forbidden",
		}, true
	case errors.Is(err, storeerr.ErrNotFound):
		return api.ErrorResponse{
			Status:  http.StatusNotFound,
			Message: err.Error(),
			Code:    "email_connection_not_found",
		}, true
	case errors.Is(err, storeerr.ErrConflict):
		return api.ErrorResponse{
			Status:  http.StatusConflict,
			Message: err.Error(),
			Code:    "email_connection_conflict",
		}, true
	default:
		return api.ErrorResponse{
			Status:  http.StatusBadGateway,
			Message: "Email server connection failed",
			Code:    "email_upstream_failed",
		}, true
	}
}
