package app

import (
	"errors"
	"net/http"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/service/app/installation"
	storeapp "github.com/tidbcloud/AgentTeam/internal/store/app"
)

// InstallationAccessGrantsList returns the ACL rows of one installation for
// its owner or a manage grantee.
func (handler *WorkspaceAppHandler) InstallationAccessGrantsList(r api.WorkspaceScopedRequest) (api.Response, error) {
	if err := handler.requireInstallations(); err != nil {
		return api.Response{}, err
	}
	installationID := strings.TrimSpace(r.Gin.Param("installationId"))
	if installationID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "installationId is required")
	}
	authorizations, err := handler.installations.ListAccess(r.Context(), r.UserID(), r.WorkspaceID(), installationID)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToInstallationAccessGrantResponses(authorizations)), nil
}

// InstallationAccessGrantCreate grants use or manage access on one
// installation to a human member or an agent.
func (handler *WorkspaceAppHandler) InstallationAccessGrantCreate(r api.WorkspaceScopedJSON[dto.InstallationAccessGrantCreateRequest]) (api.Response, error) {
	if err := handler.requireInstallations(); err != nil {
		return api.Response{}, err
	}
	installationID := strings.TrimSpace(r.Gin.Param("installationId"))
	if installationID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "installationId is required")
	}
	authorization, err := handler.installations.GrantAccess(r.Context(), r.UserID(), r.WorkspaceID(), installationID, installation.AccessGrantParams{
		PrincipalKind: r.Body.PrincipalKind,
		PrincipalRef:  r.Body.PrincipalRef,
		AccessRole:    r.Body.AccessRole,
		Scopes:        r.Body.Scopes,
	})
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToInstallationAccessGrantResponse(authorization)), nil
}

// InstallationAccessGrantDelete revokes one ACL row of an installation.
func (handler *WorkspaceAppHandler) InstallationAccessGrantDelete(r api.WorkspaceScopedRequest) (api.Response, error) {
	if err := handler.requireInstallations(); err != nil {
		return api.Response{}, err
	}
	installationID := strings.TrimSpace(r.Gin.Param("installationId"))
	authorizationID := strings.TrimSpace(r.Gin.Param("authorizationId"))
	if installationID == "" || authorizationID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "installationId and authorizationId are required")
	}
	if err := handler.installations.RevokeAccess(r.Context(), r.UserID(), r.WorkspaceID(), installationID, authorizationID); err != nil {
		return api.Response{}, err
	}
	return api.OK(nil), nil
}

// requireInstallations guards the ownership handlers against an unwired
// service dependency instead of nil-panicking mid-request.
func (handler *WorkspaceAppHandler) requireInstallations() error {
	if handler.installations == nil {
		return api.NewHTTPError(http.StatusServiceUnavailable, "installation service is not configured")
	}
	return nil
}

// InstallationCreate handles the workspace-admin direct App install command.
// It never creates or projects an installation approval request.
func (handler *WorkspaceAppHandler) InstallationCreate(r api.WorkspaceScopedJSON[dto.WorkspaceAppInstallationCreateRequest]) (api.Response, error) {
	// Ownership is the single discriminator: "workspace" (default) uses the
	// administrator path; "personal" routes to the installation service
	// where the caller is the owner and the installation starts private.
	// Any other value is rejected instead of silently defaulting.
	switch ownership := strings.TrimSpace(r.Body.Ownership); ownership {
	case "", "workspace":
	case "personal":
		if err := handler.requireInstallations(); err != nil {
			return api.Response{}, err
		}
		personal, err := handler.installations.InstallForSelf(r.Context(), r.UserID(), r.WorkspaceID(), installation.InstallParams{
			AppID:   r.Body.AppID,
			AppSlug: r.Body.AppSlug,
			Config:  r.Body.Config,
		})
		if err != nil {
			return api.Response{}, err
		}
		handler.emitWorkspaceAppsUpdated(r.WorkspaceID())
		return noStoreOK(converter.ToDirectPersonalInstallationResponse(personal)), nil
	default:
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "ownership must be workspace or personal")
	}
	installation, err := handler.store.InstallAppForWorkspaceAdmin(r.Context(), r.UserID(), r.WorkspaceID(), storeapp.InstallAppParams{
		AppID:   r.Body.AppID,
		AppSlug: r.Body.AppSlug,
		Config:  r.Body.Config,
	})
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(handler.toDirectAppInstallationResponse(installation)), nil
}

// InstallationRequestCreate handles POST /api/v1/workspaces/:workspaceId/app-installation-requests.
// Creates an app installation request for the authenticated user.
func (handler *WorkspaceAppHandler) InstallationRequestCreate(r api.WorkspaceScopedJSON[dto.WorkspaceAppInstallationRequestCreateRequest]) (api.Response, error) {
	request := r.Body
	// Workspace-share submissions ride the same request surface: the
	// service owns the share snapshot and approval semantics. Share rows
	// do not project into the install/configure approval cards.
	if strings.TrimSpace(request.RequestKind) == "share" {
		if err := handler.requireInstallations(); err != nil {
			return api.Response{}, err
		}
		shareRequest, err := handler.installations.RequestShare(r.Context(), r.UserID(), r.WorkspaceID(), strings.TrimSpace(request.InstallationID), request.Reason)
		if err != nil {
			return api.Response{}, err
		}
		handler.emitWorkspaceAppsUpdated(r.WorkspaceID())
		return noStoreOK(converter.ToShareRequestResponse(shareRequest)), nil
	}
	installationRequest, err := handler.store.RequestAppInstallationForUser(r.Context(), r.UserID(), r.WorkspaceID(), storeapp.RequestAppInstallationParams{
		AppID:                 request.AppID,
		AppSlug:               request.AppSlug,
		InstallationID:        request.InstallationID,
		RequestKind:           request.RequestKind,
		ConnectionTarget:      request.ConnectionTarget,
		ConnectionTargets:     request.ConnectionTargets,
		ConnectionID:          request.ConnectionID,
		ExpectedBaseConfig:    request.ExpectedBaseConfig,
		Config:                request.Config,
		GitHubSetupProposalID: request.GitHubSetupProposalID,
		Reason:                request.Reason,
	})
	if err != nil {
		return api.Response{}, err
	}
	handler.projectCommittedAppInstallationRequest(r.Context(), installationRequest)
	return api.OK(converter.ToAppInstallationRequestResponse(installationRequest)), nil
}

// InstallationRequestDecide handles POST /api/v1/workspaces/:workspaceId/app-installation-requests/:requestId:approve|reject.
func (handler *WorkspaceAppHandler) InstallationRequestDecide(r api.WorkspaceScopedJSON[dto.WorkspaceAppInstallationRequestDecisionRequest]) (api.Response, error) {
	requestID, action, err := api.ParseActionSegment(r.Gin.Param("requestAction"), "requestId")
	if err != nil {
		return api.Response{}, err
	}
	if action != "approve" && action != "reject" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "unsupported request action")
	}
	// Share decisions route to the installation service by request kind;
	// lookup errors propagate rather than silently falling through.
	existing, lookupErr := handler.store.GetAppInstallationRequest(r.Context(), r.WorkspaceID(), requestID)
	if lookupErr != nil {
		return api.Response{}, lookupErr
	}
	if strings.TrimSpace(existing.RequestKind) == "share" {
		if err := handler.requireInstallations(); err != nil {
			return api.Response{}, err
		}
		decided, err := handler.installations.DecideShare(r.Context(), r.UserID(), r.WorkspaceID(), requestID, action == "approve", r.Body.Reason)
		if err != nil {
			return api.Response{}, err
		}
		handler.emitWorkspaceAppsUpdated(r.WorkspaceID())
		return noStoreOK(converter.ToShareRequestResponse(decided)), nil
	}
	switch action {
	case "approve":
		installationRequest, err := handler.store.ApproveAppInstallationRequestForUser(r.Context(), r.UserID(), r.WorkspaceID(), requestID, storeapp.DecideAppInstallationRequestParams{
			Reason: r.Body.Reason,
		})
		if err != nil {
			if errors.Is(err, storeapp.ErrAppInstallationApprovalStale) ||
				errors.Is(err, storeapp.ErrGiteaConnectionConfigurationFailed) {
				staleRequest, markErr := handler.store.MarkAppInstallationRequestCannotBeApplied(
					r.Context(), r.WorkspaceID(), requestID,
				)
				if markErr != nil {
					return api.Response{}, errors.Join(err, markErr)
				}
				handler.projectCommittedAppInstallationRequest(r.Context(), staleRequest)
			}
			return api.Response{}, err
		}
		handler.projectCommittedAppInstallationRequest(r.Context(), installationRequest)
		handler.emitWorkspaceAppsUpdated(r.WorkspaceID())
		return api.OK(converter.ToAppInstallationRequestResponse(installationRequest)), nil
	case "reject":
		installationRequest, err := handler.store.RejectAppInstallationRequestForUser(r.Context(), r.UserID(), r.WorkspaceID(), requestID, storeapp.DecideAppInstallationRequestParams{
			Reason: r.Body.Reason,
		})
		if err != nil {
			return api.Response{}, err
		}
		handler.projectCommittedAppInstallationRequest(r.Context(), installationRequest)
		handler.emitWorkspaceAppsUpdated(r.WorkspaceID())
		return api.OK(converter.ToAppInstallationRequestResponse(installationRequest)), nil
	default:
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Unsupported app installation request action")
	}
}
