// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.

package auth

import (
	"context"
	"fmt"

	"github.com/harness/gitness/internal/auth"
	"github.com/harness/gitness/internal/auth/authz"
	"github.com/harness/gitness/internal/paths"
	"github.com/harness/gitness/internal/store"
	"github.com/harness/gitness/types"
	"github.com/harness/gitness/types/enum"

	"github.com/pkg/errors"
	"github.com/rs/zerolog/log"
)

var (
	ErrNotAuthenticated          = errors.New("not authenticated")
	ErrNotAuthorized             = errors.New("not authorized")
	ErrParentResourceTypeUnknown = errors.New("Unknown parent resource type")
)

/*
 * Check checks if a resource specific permission is granted for the current auth session in the scope.
 * Returns nil if the permission is granted, otherwise returns an error.
 * NotAuthenticated, NotAuthorized, or any unerlaying error.
 */
func Check(ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
	scope *types.Scope, resource *types.Resource, permission enum.Permission) error {
	if session == nil {
		return ErrNotAuthenticated
	}

	authorized, err := authorizer.Check(
		ctx,
		session,
		scope,
		resource,
		permission)
	if err != nil {
		return err
	}

	if !authorized {
		return ErrNotAuthorized
	}

	return nil
}

/*
 * CheckChild checks if a resource specific permission is granted for the current auth session
 * in the scope of a parent.
 * Returns nil if the permission is granted, otherwise returns an error.
 * NotAuthenticated, NotAuthorized, or any unerlaying error.
 */
func CheckChild(ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
	spaceStore store.SpaceStore, repoStore store.RepoStore, parentType enum.ParentResourceType, parentID int64,
	resourceType enum.ResourceType, resourceName string, permission enum.Permission) error {
	scope, err := getScopeForParent(ctx, spaceStore, repoStore, parentType, parentID)
	if err != nil {
		return err
	}

	resource := &types.Resource{
		Type: resourceType,
		Name: resourceName,
	}

	return Check(ctx, authorizer, session, scope, resource, permission)
}

// getScopeForParent Returns the scope for a given resource parent (space or repo).
func getScopeForParent(ctx context.Context, spaceStore store.SpaceStore, repoStore store.RepoStore,
	parentType enum.ParentResourceType, parentID int64) (*types.Scope, error) {
	// TODO: Can this be done cleaner?
	switch parentType {
	case enum.ParentResourceTypeSpace:
		space, err := spaceStore.Find(ctx, parentID)
		if err != nil {
			return nil, fmt.Errorf("parent space not found: %w", err)
		}

		return &types.Scope{SpacePath: space.Path}, nil

	case enum.ParentResourceTypeRepo:
		repo, err := repoStore.Find(ctx, parentID)
		if err != nil {
			return nil, fmt.Errorf("parent repo not found: %w", err)
		}

		spacePath, repoName, err := paths.DisectLeaf(repo.Path)
		if err != nil {
			return nil, errors.Wrapf(err, "Failed to disect path '%s'", repo.Path)
		}

		return &types.Scope{SpacePath: spacePath, Repo: repoName}, nil

	default:
		log.Ctx(ctx).Debug().Msgf("Unsupported parent type encountered: '%s'", parentType)

		return nil, ErrParentResourceTypeUnknown
	}
}