// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package controller

import (
	"context"
	"fmt"

	"github.com/harness/gitness/app/auth"
	"github.com/harness/gitness/app/bootstrap"
	"github.com/harness/gitness/app/githook"
	"github.com/harness/gitness/app/url"
	"github.com/harness/gitness/git"
	"github.com/harness/gitness/types"
)

// createRPCWriteParams creates base write parameters for git write operations.
// TODO: this function should be in git package and should accept params as interface (contract)
func createRPCWriteParams(
	ctx context.Context,
	urlProvider url.Provider,
	session *auth.Session,
	repo *types.Repository,
	isInternal bool,
) (git.WriteParams, error) {
	// generate envars (add everything githook CLI needs for execution)
	envVars, err := githook.GenerateEnvironmentVariables(
		ctx,
		urlProvider.GetInternalAPIURL(ctx),
		repo.ID,
		session.Principal.ID,
		false,
		isInternal,
	)
	if err != nil {
		return git.WriteParams{}, fmt.Errorf("failed to generate git hook environment variables: %w", err)
	}

	return git.WriteParams{
		Actor: git.Identity{
			Name:  session.Principal.DisplayName,
			Email: session.Principal.Email,
		},
		RepoUID: repo.GitUID,
		EnvVars: envVars,
	}, nil
}

// CreateRPCExternalWriteParams creates base write parameters for git external write operations.
// External write operations are direct git pushes.
func CreateRPCExternalWriteParams(
	ctx context.Context,
	urlProvider url.Provider,
	session *auth.Session,
	repo *types.Repository,
) (git.WriteParams, error) {
	return createRPCWriteParams(ctx, urlProvider, session, repo, false)
}

// CreateRPCInternalWriteParams creates base write parameters for git internal write operations.
// Internal write operations are git pushes that originate from the Harness server.
func CreateRPCInternalWriteParams(
	ctx context.Context,
	urlProvider url.Provider,
	session *auth.Session,
	repo *types.Repository,
) (git.WriteParams, error) {
	return createRPCWriteParams(ctx, urlProvider, session, repo, true)
}

func MapBranch(b git.Branch) (types.Branch, error) {
	var commit *types.Commit
	if b.Commit != nil {
		var err error
		commit, err = MapCommit(b.Commit)
		if err != nil {
			return types.Branch{}, err
		}
	}
	return types.Branch{
		Name:   b.Name,
		SHA:    b.SHA,
		Commit: commit,
	}, nil
}

func MapCommit(c *git.Commit) (*types.Commit, error) {
	if c == nil {
		return nil, fmt.Errorf("commit is nil")
	}

	author, err := MapSignature(&c.Author)
	if err != nil {
		return nil, fmt.Errorf("failed to map author: %w", err)
	}

	committer, err := MapSignature(&c.Committer)
	if err != nil {
		return nil, fmt.Errorf("failed to map committer: %w", err)
	}

	parentSHAs := make([]string, len(c.ParentSHAs))
	for i, sha := range c.ParentSHAs {
		parentSHAs[i] = sha.String()
	}

	return &types.Commit{
			SHA:        c.SHA.String(),
			ParentSHAs: parentSHAs,
			Title:      c.Title,
			Message:    c.Message,
			Author:     *author,
			Committer:  *committer,
			Stats:      mapStats(c),
		},
		nil
}

func mapStats(c *git.Commit) *types.CommitStats {
	if len(c.FileStats) == 0 {
		return nil
	}

	var insertions int64
	var deletions int64
	for _, stat := range c.FileStats {
		insertions += stat.Insertions
		deletions += stat.Deletions
	}

	return &types.CommitStats{
		Total: types.ChangeStats{
			Insertions: insertions,
			Deletions:  deletions,
			Changes:    insertions + deletions,
		},
		Files: mapFileStats(c),
	}
}

func mapFileStats(c *git.Commit) []types.CommitFileStats {
	fileStats := make([]types.CommitFileStats, len(c.FileStats))

	for i, fStat := range c.FileStats {
		fileStats[i] = types.CommitFileStats{
			Path:    fStat.Path,
			OldPath: fStat.OldPath,
			Status:  fStat.Status,
			ChangeStats: types.ChangeStats{
				Insertions: fStat.Insertions,
				Deletions:  fStat.Deletions,
				Changes:    fStat.Insertions + fStat.Deletions,
			},
		}
	}

	return fileStats
}

func MapRenameDetails(c *git.RenameDetails) *types.RenameDetails {
	if c == nil {
		return nil
	}
	return &types.RenameDetails{
		OldPath:         c.OldPath,
		NewPath:         c.NewPath,
		CommitShaBefore: c.CommitShaBefore.String(),
		CommitShaAfter:  c.CommitShaAfter.String(),
	}
}

func MapSignature(s *git.Signature) (*types.Signature, error) {
	if s == nil {
		return nil, fmt.Errorf("signature is nil")
	}

	return &types.Signature{
		Identity: types.Identity{
			Name:  s.Identity.Name,
			Email: s.Identity.Email,
		},
		When: s.When,
	}, nil
}

func IdentityFromPrincipalInfo(p types.PrincipalInfo) *git.Identity {
	return &git.Identity{
		Name:  p.DisplayName,
		Email: p.Email,
	}
}

func SystemServicePrincipalInfo() *git.Identity {
	return IdentityFromPrincipalInfo(*bootstrap.NewSystemServiceSession().Principal.ToPrincipalInfo())
}