package permissions

import (
	"context"
	"fmt"
	"sort"

	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

const resourceAccessTypeFolder = "folder"

// ContractResourceAccess exposes generic resource ACL grants through the
// neutral contract seam.
type ContractResourceAccess struct {
	folderAccess *FolderAccessService
}

func NewContractResourceAccess(folderAccess *FolderAccessService) *ContractResourceAccess {
	return &ContractResourceAccess{folderAccess: folderAccess}
}

func (a *ContractResourceAccess) ListVisibleResourceIDs(ctx context.Context, user *models.UserSession, req contract.ResourceVisibilityRequest) ([]string, error) {
	if err := a.ready(req.ResourceType); err != nil {
		return nil, err
	}
	return a.folderAccess.VisibleFolderIDs(ctx, user)
}

func (a *ContractResourceAccess) ListResourceAccessGrants(ctx context.Context, _ *models.UserSession, req contract.ResourceAccessListRequest) ([]contract.ResourceAccessGrant, error) {
	if err := a.ready(req.ResourceType); err != nil {
		return nil, err
	}
	rows, err := a.folderAccess.ListShares(ctx, req.ResourceID)
	if err != nil {
		return nil, err
	}
	grants := make([]contract.ResourceAccessGrant, 0, len(rows))
	for _, row := range rows {
		grants = append(grants, folderShareRowToResourceGrant(req.ResourceType, row))
	}
	sort.Slice(grants, func(i, j int) bool {
		return grants[i].ID < grants[j].ID
	})
	return grants, nil
}

func (a *ContractResourceAccess) GetResourceAccessGrant(ctx context.Context, _ *models.UserSession, req contract.ResourceAccessGetRequest) (*contract.ResourceAccessGrant, error) {
	if err := a.ready(req.ResourceType); err != nil {
		return nil, err
	}
	row, err := a.folderAccess.GetShare(ctx, req.GrantID)
	if err != nil {
		return nil, err
	}
	grant := folderShareRowToResourceGrant(req.ResourceType, *row)
	return &grant, nil
}

func (a *ContractResourceAccess) CreateResourceAccessGrant(ctx context.Context, _ *models.UserSession, req contract.ResourceAccessCreateRequest) (*contract.ResourceAccessGrant, error) {
	if err := a.ready(req.ResourceType); err != nil {
		return nil, err
	}
	id, err := a.folderAccess.InsertShare(ctx, req.ResourceID, req.Grant.PrincipalID, req.Grant.PrincipalType, req.Grant.AccessLevel, req.CreatedByID)
	if err != nil {
		return nil, err
	}
	return &contract.ResourceAccessGrant{
		ID:            id,
		ResourceType:  req.ResourceType,
		ResourceID:    req.ResourceID,
		PrincipalID:   req.Grant.PrincipalID,
		PrincipalType: req.Grant.PrincipalType,
		AccessLevel:   req.Grant.AccessLevel,
	}, nil
}

func (a *ContractResourceAccess) EnsureResourceAccessGrant(ctx context.Context, _ *models.UserSession, req contract.ResourceAccessEnsureRequest) (*contract.ResourceAccessGrant, error) {
	if err := a.ready(req.ResourceType); err != nil {
		return nil, err
	}
	rows, err := a.folderAccess.ListShares(ctx, req.ResourceID)
	if err != nil {
		return nil, err
	}
	for _, row := range rows {
		if row.SharedToType == req.Grant.PrincipalType {
			if req.Grant.PrincipalType == "Organization" || row.SharedToID == req.Grant.PrincipalID {
				grant := folderShareRowToResourceGrant(req.ResourceType, row)
				return &grant, nil
			}
		}
	}
	return a.CreateResourceAccessGrant(ctx, nil, contract.ResourceAccessCreateRequest{
		ResourceType: req.ResourceType,
		ResourceID:   req.ResourceID,
		CreatedByID:  req.CreatedByID,
		Grant:        req.Grant,
	})
}

func (a *ContractResourceAccess) ReplaceResourceAccessGrants(ctx context.Context, _ *models.UserSession, req contract.ResourceAccessReplaceRequest) ([]contract.ResourceAccessGrant, error) {
	if err := a.ready(req.ResourceType); err != nil {
		return nil, err
	}
	inputs := make([]ShareInput, 0, len(req.Grants))
	for _, grant := range req.Grants {
		inputs = append(inputs, ShareInput{
			SharedToID:   grant.PrincipalID,
			SharedToType: grant.PrincipalType,
			AccessLevel:  grant.AccessLevel,
		})
	}
	ids, err := a.folderAccess.ReplaceAllShares(ctx, req.ResourceID, req.CreatedByID, inputs)
	if err != nil {
		return nil, err
	}
	grants := make([]contract.ResourceAccessGrant, 0, len(inputs))
	for i, input := range inputs {
		grants = append(grants, contract.ResourceAccessGrant{
			ID:            ids[i],
			ResourceType:  req.ResourceType,
			ResourceID:    req.ResourceID,
			PrincipalID:   input.SharedToID,
			PrincipalType: input.SharedToType,
			AccessLevel:   input.AccessLevel,
		})
	}
	return grants, nil
}

func (a *ContractResourceAccess) UpdateResourceAccessGrant(ctx context.Context, _ *models.UserSession, req contract.ResourceAccessUpdateRequest) (*contract.ResourceAccessGrant, error) {
	if err := a.ready(req.ResourceType); err != nil {
		return nil, err
	}
	if err := a.folderAccess.UpdateShareAccessLevel(ctx, req.GrantID, req.AccessLevel); err != nil {
		return nil, err
	}
	row, err := a.folderAccess.GetShare(ctx, req.GrantID)
	if err != nil {
		return nil, err
	}
	grant := folderShareRowToResourceGrant(req.ResourceType, *row)
	return &grant, nil
}

func (a *ContractResourceAccess) DeleteResourceAccessGrant(ctx context.Context, _ *models.UserSession, req contract.ResourceAccessDeleteRequest) error {
	if err := a.ready(req.ResourceType); err != nil {
		return err
	}
	return a.folderAccess.DeleteShare(ctx, req.GrantID)
}

func (a *ContractResourceAccess) DeleteResourceAccessGrantsByPrincipalType(ctx context.Context, _ *models.UserSession, req contract.ResourceAccessDeleteByPrincipalTypeRequest) error {
	if err := a.ready(req.ResourceType); err != nil {
		return err
	}
	return a.folderAccess.DeleteSharesByType(ctx, req.ResourceID, req.PrincipalType)
}

func (a *ContractResourceAccess) TransferResourceOwnership(ctx context.Context, _ *models.UserSession, req contract.ResourceOwnershipTransferRequest) error {
	if err := a.ready(req.ResourceType); err != nil {
		return err
	}
	return a.folderAccess.TransferFolderOwnership(ctx, req.SourceOwnerID, req.TargetOwnerID)
}

func (a *ContractResourceAccess) ready(resourceType string) error {
	if a == nil || a.folderAccess == nil {
		return fmt.Errorf("resource access service unavailable")
	}
	if resourceType != resourceAccessTypeFolder {
		return fmt.Errorf("unsupported resource access type %q", resourceType)
	}
	return nil
}

func folderShareRowToResourceGrant(resourceType string, row FolderShareRow) contract.ResourceAccessGrant {
	return contract.ResourceAccessGrant{
		ID:            row.ID,
		ResourceType:  resourceType,
		ResourceID:    row.FolderID,
		PrincipalID:   row.SharedToID,
		PrincipalType: row.SharedToType,
		AccessLevel:   row.AccessLevel,
	}
}
