// 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 gitrpc

import (
	"context"
	"fmt"
	"time"

	"github.com/harness/gitness/gitrpc/rpc"

	gonanoid "github.com/matoous/go-nanoid/v2"
	"github.com/rs/zerolog/log"
)

const (
	// repoGitUIDLength is the length of the generated repo uid.
	repoGitUIDLength = 42

	// repoGitUIDAlphabet is the alphabet used for generating repo uids
	// NOTE: keep it lowercase and alphanumerical to avoid issues with case insensitive filesystems.
	repoGitUIDAlphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
)

type CreateRepositoryParams struct {
	// Create operation is different from all (from user side), as UID doesn't exist yet.
	// Only take actor as input and create WriteParams manually
	Actor         Identity
	DefaultBranch string
	Files         []File

	// Committer overwrites the git committer used for committing the files (optional, default: actor)
	Committer *Identity
	// Author overwrites the git author used for committing the files (optional, default: committer)
	Author *Identity
}

type CreateRepositoryOutput struct {
	UID string
}

type DeleteRepositoryParams struct {
	WriteParams
}

func (c *Client) CreateRepository(ctx context.Context,
	params *CreateRepositoryParams) (*CreateRepositoryOutput, error) {
	if params == nil {
		return nil, ErrNoParamsProvided
	}

	log := log.Ctx(ctx)

	uid, err := newRepositoryUID()
	if err != nil {
		return nil, fmt.Errorf("failed to create new uid: %w", err)
	}
	log.Info().
		Msgf("Create new git repository with uid '%s' and default branch '%s'", uid, params.DefaultBranch)

	ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
	defer cancel()
	stream, err := c.repoService.CreateRepository(ctx)
	if err != nil {
		return nil, err
	}

	log.Info().Msgf("Send header")

	writeParams := WriteParams{
		RepoUID: uid,
		Actor:   params.Actor,
		EnvVars: map[string]string{}, // (no githook triggered for repo creation)
	}

	req := &rpc.CreateRepositoryRequest{
		Data: &rpc.CreateRepositoryRequest_Header{
			Header: &rpc.CreateRepositoryRequestHeader{
				Base:          mapToRPCWriteRequest(writeParams),
				DefaultBranch: params.DefaultBranch,
				Author:        mapToRPCIdentityOptional(params.Author),
				Committer:     mapToRPCIdentityOptional(params.Committer),
			},
		},
	}
	if err = stream.Send(req); err != nil {
		return nil, err
	}

	for _, file := range params.Files {
		log.Info().Msgf("Send file %s", file.Path)

		err = uploadFile(ctx, file, FileTransferChunkSize, func(fs *rpc.FileUpload) error {
			return stream.Send(&rpc.CreateRepositoryRequest{
				Data: &rpc.CreateRepositoryRequest_File{
					File: fs,
				},
			})
		})
		if err != nil {
			return nil, err
		}
	}

	_, err = stream.CloseAndRecv()
	if err != nil {
		return nil, processRPCErrorf(err, "failed to create repo on server (uid: '%s')", uid)
	}

	log.Info().Msgf("completed git repo setup.")

	return &CreateRepositoryOutput{UID: uid}, nil
}

func newRepositoryUID() (string, error) {
	return gonanoid.Generate(repoGitUIDAlphabet, repoGitUIDLength)
}

func (c *Client) DeleteRepository(ctx context.Context, params *DeleteRepositoryParams) error {
	if params == nil {
		return ErrNoParamsProvided
	}
	_, err := c.repoService.DeleteRepository(ctx, &rpc.DeleteRepositoryRequest{
		Base: mapToRPCWriteRequest(params.WriteParams),
	})
	if err != nil {
		return processRPCErrorf(err, "failed to delete repository on server")
	}
	return nil
}
