// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

//go:build !gogit

package git

import (
	"context"
	"errors"
	"io"
	"strings"

	"gitea.dev/modules/git/gitcmd"
	"gitea.dev/modules/log"
)

// ResolveReference resolves a name to a reference
func (repo *Repository) ResolveReference(ctx context.Context, name string) (string, error) {
	stdout, _, err := gitcmd.NewCommand("show-ref", "--hash").
		AddDynamicArguments(name).
		WithRepo(repo).
		RunStdString(ctx)
	if err != nil {
		if strings.Contains(err.Error(), "not a valid ref") {
			return "", ErrNotExist{name, ""}
		}
		return "", err
	}
	stdout = strings.TrimSpace(stdout)
	if stdout == "" {
		return "", ErrNotExist{name, ""}
	}

	return stdout, nil
}

// GetRefCommitID returns the last commit ID string of given reference (branch or tag).
func (repo *Repository) GetRefCommitID(ctx context.Context, name string) (string, error) {
	batch, cancel, err := repo.CatFileBatch()
	if err != nil {
		return "", err
	}
	defer cancel()
	info, err := batch.QueryInfo(name)
	if IsErrNotExist(err) {
		return "", ErrNotExist{name, ""}
	} else if err != nil {
		return "", err
	}
	return info.ID, nil
}

func (repo *Repository) getCommit(ctx context.Context, id ObjectID) (*Commit, error) {
	batch, cancel, err := repo.CatFileBatch()
	if err != nil {
		return nil, err
	}
	defer cancel()
	return repo.getCommitWithBatch(batch, id)
}

func (repo *Repository) getCommitWithBatch(batch CatFileBatch, id ObjectID) (*Commit, error) {
	info, rd, err := batch.QueryContent(id.String())
	if err != nil {
		if errors.Is(err, io.EOF) || IsErrNotExist(err) {
			return nil, ErrNotExist{ID: id.String()}
		}
		return nil, err
	}

	switch info.Type {
	case "missing":
		return nil, ErrNotExist{ID: id.String()}
	case "tag":
		// then we need to parse the tag
		// and load the commit
		data, err := io.ReadAll(io.LimitReader(rd, info.Size))
		if err != nil {
			return nil, err
		}
		_, err = rd.Discard(1)
		if err != nil {
			return nil, err
		}
		tag, err := parseTagData(id.Type(), data)
		if err != nil {
			return nil, err
		}
		return repo.getCommitWithBatch(batch, tag.Object)
	case "commit":
		commit, err := CommitFromReader(id, io.LimitReader(rd, info.Size))
		if err != nil {
			return nil, err
		}
		_, err = rd.Discard(1)
		if err != nil {
			return nil, err
		}

		return commit, nil
	default:
		log.Debug("Unknown cat-file object type: %s", info.Type)
		if err := DiscardFull(rd, info.Size+1); err != nil {
			return nil, err
		}
		return nil, ErrNotExist{
			ID: id.String(),
		}
	}
}

// ConvertToGitID returns a git object ID from the git ref, it doesn't guarantee the returned ID really exists
func (repo *Repository) ConvertToGitID(ctx context.Context, ref string) (ObjectID, error) {
	objectFormat, err := repo.GetObjectFormat(ctx)
	if err != nil {
		return nil, err
	}
	if len(ref) == objectFormat.FullLength() && objectFormat.IsValid(ref) {
		id, err := NewIDFromString(ref)
		if err == nil {
			return id, nil
		}
	}

	batch, cancel, err := repo.CatFileBatch()
	if err != nil {
		return nil, err
	}
	defer cancel()
	info, err := batch.QueryInfo(ref)
	if err != nil {
		if IsErrNotExist(err) {
			return nil, ErrNotExist{ref, ""}
		}
		return nil, err
	}

	return MustIDFromString(info.ID), nil
}
