package installation

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/app/installation"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// requireManagement loads the installation and requires manage authority:
// the personal owner, a manage grant on a personal installation, or (for
// workspace installations) the legacy administrator path.
func (svc *Service) requireManagement(ctx context.Context, userID string, workspaceID string, installationID string) (access.HumanWorkspaceMember, serviceactor.InstallationSummary, error) {
	workspaceMember, err := svc.loadAuthorizedMember(ctx, svc.db, userID, workspaceID, false)
	if err != nil {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, err
	}
	installationID = strings.TrimSpace(installationID)
	if installationID == "" {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, storeerr.BadRequest("installationId is required")
	}
	summary, err := svc.actors.LoadInstallationSummaryByKind(ctx, workspaceID, installationID, serviceactor.KindApp)
	if err != nil {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, err
	}
	if summary.Installation.Status != serviceactor.StatusActive {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, storeerr.ErrNotFound
	}
	if err := svc.authorizeManagement(ctx, svc.db, workspaceID, workspaceMember, summary); err != nil {
		return access.HumanWorkspaceMember{}, serviceactor.InstallationSummary{}, err
	}
	return workspaceMember, summary, nil
}

func (svc *Service) authorizeManagement(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
	workspaceMember access.HumanWorkspaceMember,
	summary serviceactor.InstallationSummary,
) error {
	if summary.Installation.OwnershipKind != serviceactor.OwnershipKindPersonal {
		return authorizeLegacyManagement(ctx, queryer, workspaceID, workspaceMember, summary.Definition.ID)
	}
	memberID := strings.TrimSpace(workspaceMember.Member.ID)
	if summary.Installation.OwnerMemberID == memberID {
		return nil
	}
	accessRole, granted, err := svc.actors.ResolveHumanInstallationAccess(ctx, workspaceID, memberID, summary.Installation.ID)
	if err != nil {
		return err
	}
	if !granted || accessRole != serviceactor.AccessRoleManage {
		return storeerr.ErrNotFound
	}
	return nil
}

// revalidateManagement re-verifies manage authority inside a write
// transaction under the member and installation row locks.
func (svc *Service) revalidateManagement(
	ctx context.Context,
	queryer coredb.QueryRower,
	userID string,
	workspaceID string,
	expectedMemberID string,
	summary serviceactor.InstallationSummary,
) (access.HumanWorkspaceMember, error) {
	currentMember, err := svc.loadAuthorizedMember(ctx, queryer, userID, workspaceID, true)
	if err != nil {
		return access.HumanWorkspaceMember{}, err
	}
	if strings.TrimSpace(currentMember.Member.ID) != strings.TrimSpace(expectedMemberID) {
		return access.HumanWorkspaceMember{}, storeerr.Conflict("App installation manager membership changed.")
	}
	var ownershipKind, ownerMemberID string
	err = queryer.QueryRow(ctx, `
SELECT ownership_kind, COALESCE(owner_member_id, '')
FROM service_installations
WHERE id = $1
  AND workspace_id = $2
  AND service_kind = 'app'
LIMIT 1
FOR UPDATE
`, strings.TrimSpace(summary.Installation.ID), strings.TrimSpace(workspaceID)).Scan(&ownershipKind, &ownerMemberID)
	if err != nil {
		if coredb.IsNotFound(err) {
			return access.HumanWorkspaceMember{}, storeerr.ErrNotFound
		}
		return access.HumanWorkspaceMember{}, fmt.Errorf("revalidate App installation ownership: %w", err)
	}
	if strings.TrimSpace(ownershipKind) != serviceactor.OwnershipKindPersonal {
		return currentMember, authorizeLegacyManagement(ctx, queryer, workspaceID, currentMember, summary.Definition.ID)
	}
	memberID := strings.TrimSpace(currentMember.Member.ID)
	if strings.TrimSpace(ownerMemberID) == memberID {
		return currentMember, nil
	}
	accessRole, granted, err := svc.actors.InTx(queryer.(coredb.Tx)).ResolveHumanInstallationAccessForUpdate(ctx, workspaceID, memberID, summary.Installation.ID)
	if err != nil {
		return access.HumanWorkspaceMember{}, err
	}
	if !granted || accessRole != serviceactor.AccessRoleManage {
		return access.HumanWorkspaceMember{}, storeerr.ErrNotFound
	}
	return currentMember, nil
}

// authorizeLegacyManagement preserves the pre-ownership administrator path
// for workspace installations: unattributed Apps need the workspace
// service-installer permission; attributed Apps accept administrators and
// active App managers.
func authorizeLegacyManagement(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
	workspaceMember access.HumanWorkspaceMember,
	serviceDefinitionID string,
) error {
	_, attributed, err := installation.LoadAppManagementAttribution(ctx, queryer, workspaceID, serviceDefinitionID, true)
	if err != nil {
		return err
	}
	if !attributed {
		if err := access.AuthorizeHumanAction(workspaceMember, workspaceInstallServiceAction); err != nil {
			return storeerr.ErrForbidden
		}
		return nil
	}
	if hasWorkspaceAdminRole(workspaceMember) {
		return nil
	}
	managed, err := installation.HasActiveAppManager(ctx, queryer, workspaceID, serviceDefinitionID, workspaceMember.Member.ID, true)
	if err != nil {
		return err
	}
	if !managed {
		return storeerr.ErrNotFound
	}
	return nil
}

func hasWorkspaceAdminRole(member access.HumanWorkspaceMember) bool {
	return access.HasWorkspaceAdminRole(domain.WorkspaceRole(member.WorkspaceRole))
}

var workspaceInstallServiceAction = access.HumanAction{
	Resource: access.ResourceWorkspace,
	Action:   access.ActionInstallService,
}

// loadAuthorizedMember maps the access package's sentinel errors onto the
// store error contract the API error classifier understands.
func (svc *Service) loadAuthorizedMember(ctx context.Context, queryer coredb.QueryRower, userID string, workspaceID string, forUpdate bool) (access.HumanWorkspaceMember, error) {
	var (
		member access.HumanWorkspaceMember
		err    error
	)
	if forUpdate {
		member, err = access.LoadAuthorizedHumanWorkspaceMemberForUpdate(ctx, queryer, userID, workspaceID, workspaceAccessAction)
	} else {
		member, err = access.LoadAuthorizedHumanWorkspaceMember(ctx, queryer, userID, workspaceID, workspaceAccessAction)
	}
	if err != nil {
		return access.HumanWorkspaceMember{}, mapAccessError(err)
	}
	return member, nil
}

func mapAccessError(err error) error {
	if errors.Is(err, access.ErrForbidden) {
		return storeerr.ErrForbidden
	}
	if errors.Is(err, access.ErrNotFound) {
		return storeerr.ErrNotFound
	}
	return err
}
