package server

import (
	"context"
	"errors"
	"fmt"

	"github.com/go-git/go-git/v5/plumbing"
	"github.com/pingcap/agent-git-service/internal/gitstore"
)

var (
	// ErrRepositorySnapshotEscape is returned when a requested object is not a
	// regular file in the immutable Git tree. Symbolic links are never followed.
	ErrRepositorySnapshotEscape = gitstore.ErrRepositorySnapshotEscape
	// ErrRepositorySnapshotResourceExhausted reports a host protection limit,
	// not an incomplete successful search.
	ErrRepositorySnapshotResourceExhausted = gitstore.ErrRepositorySnapshotResourceExhausted
	// ErrRepositorySnapshotRefNotFound reports that an otherwise valid branch
	// or tag does not exist in the selected repository.
	ErrRepositorySnapshotRefNotFound = errors.New("repository snapshot ref not found")
	// ErrRepositorySnapshotRefInvalid reports a selector outside the narrow
	// branch/tag contract or conservative Git ref-name subset.
	ErrRepositorySnapshotRefInvalid = errors.New("repository snapshot ref invalid")
)

const (
	RepositorySnapshotRefBranch = "branch"
	RepositorySnapshotRefTag    = "tag"
)

type RepositorySnapshotGrepRequest = gitstore.RepositorySnapshotGrepRequest
type RepositorySnapshotGrepMatch = gitstore.RepositorySnapshotGrepMatch
type RepositorySnapshotListRequest = gitstore.RepositorySnapshotListRequest
type RepositorySnapshotListEntry = gitstore.RepositorySnapshotListEntry
type RepositorySnapshotReadRequest = gitstore.RepositorySnapshotReadRequest
type RepositorySnapshotReadChunk = gitstore.RepositorySnapshotReadChunk

type RepositorySnapshotRefRequest struct {
	RepositoryFullName string
	RefName            string
	RefType            string
}

// GrepRepositorySnapshot streams git-grep matches from the exact authorized
// commit. Product authorization remains in the embedding host; this primitive
// accepts an AGS repository identity, never a caller-controlled filesystem path.
func (s *Server) GrepRepositorySnapshot(ctx context.Context, request RepositorySnapshotGrepRequest, emit func(RepositorySnapshotGrepMatch) error) error {
	store, err := s.repositorySnapshotStore()
	if err != nil {
		return err
	}
	return store.GrepRepositorySnapshot(ctx, request, emit)
}

// ListRepositorySnapshot lists one tree level at the exact authorized commit.
func (s *Server) ListRepositorySnapshot(ctx context.Context, request RepositorySnapshotListRequest, emit func(RepositorySnapshotListEntry) error) error {
	store, err := s.repositorySnapshotStore()
	if err != nil {
		return err
	}
	return store.ListRepositorySnapshot(ctx, request, emit)
}

// ReadRepositorySnapshot streams a line range from a regular blob at the exact
// authorized commit. It never checks out a worktree or follows a symlink.
func (s *Server) ReadRepositorySnapshot(ctx context.Context, request RepositorySnapshotReadRequest, emit func(RepositorySnapshotReadChunk) error) error {
	store, err := s.repositorySnapshotStore()
	if err != nil {
		return err
	}
	return store.ReadRepositorySnapshot(ctx, request, emit)
}

// ResolveRepositorySnapshotRef resolves one explicit branch or tag in an
// already product-authorized repository to an immutable commit. It never
// accepts revision expressions, remote refs, or filesystem paths. Annotated
// tags are peeled to their commit before the revision crosses this boundary.
func (s *Server) ResolveRepositorySnapshotRef(ctx context.Context, request RepositorySnapshotRefRequest) (string, error) {
	store, err := s.repositorySnapshotStore()
	if err != nil {
		return "", err
	}
	if !gitstore.IsValidRefName(request.RefName) {
		return "", ErrRepositorySnapshotRefInvalid
	}
	switch request.RefType {
	case RepositorySnapshotRefBranch:
		sha, err := store.HeadSHA(ctx, request.RepositoryFullName, request.RefName)
		if errors.Is(err, plumbing.ErrReferenceNotFound) {
			return "", ErrRepositorySnapshotRefNotFound
		}
		return sha, err
	case RepositorySnapshotRefTag:
		ref := plumbing.NewTagReferenceName(request.RefName).String()
		if _, err := store.LookupRef(ctx, request.RepositoryFullName, ref); errors.Is(err, gitstore.ErrRefNotFound) {
			return "", ErrRepositorySnapshotRefNotFound
		} else if err != nil {
			return "", err
		}
		return store.ResolveContentCommit(ctx, request.RepositoryFullName, ref)
	default:
		return "", ErrRepositorySnapshotRefInvalid
	}
}

func (s *Server) repositorySnapshotStore() (*gitstore.Store, error) {
	if s == nil || s.deps == nil || s.deps.Store == nil {
		return nil, fmt.Errorf("ags server is not initialized")
	}
	return s.deps.Store, nil
}
