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

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/harness/gitness/app/api/usererror"
	gittypes "github.com/harness/gitness/git/api"
	"github.com/harness/gitness/types"
	"github.com/harness/gitness/types/enum"
)

const (
	HeaderParamGitProtocol = "Git-Protocol"

	PathParamCommitSHA = "commit_sha"

	QueryParamGitRef             = "git_ref"
	QueryParamIncludeCommit      = "include_commit"
	QueryParamIncludeDirectories = "include_directories"
	QueryParamFlattenDirectories = "flatten_directories"
	QueryParamLineFrom           = "line_from"
	QueryParamLineTo             = "line_to"
	QueryParamPath               = "path"
	QueryParamSince              = "since"
	QueryParamUntil              = "until"
	QueryParamCommitter          = "committer"
	QueryParamCommitterID        = "committer_id"
	QueryParamAuthor             = "author"
	QueryParamAuthorID           = "author_id"

	QueryParamIncludeStats = "include_stats"
	QueryParamInternal     = "internal"
	QueryParamService      = "service"
	QueryParamCommitSHA    = "commit_sha"

	QueryParamIncludeChecks   = "include_checks"
	QueryParamIncludeRules    = "include_rules"
	QueryParamIncludePullReqs = "include_pullreqs"
	QueryParamMaxDivergence   = "max_divergence"
)

func GetGitRefFromQueryOrDefault(r *http.Request, deflt string) string {
	return QueryParamOrDefault(r, QueryParamGitRef, deflt)
}

func GetIncludeCommitFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
	return QueryParamAsBoolOrDefault(r, QueryParamIncludeCommit, deflt)
}

func GetIncludeChecksFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
	return QueryParamAsBoolOrDefault(r, QueryParamIncludeChecks, deflt)
}

func GetIncludeRulesFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
	return QueryParamAsBoolOrDefault(r, QueryParamIncludeRules, deflt)
}

func GetIncludePullReqsFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
	return QueryParamAsBoolOrDefault(r, QueryParamIncludePullReqs, deflt)
}

func GetMaxDivergenceFromQueryOrDefault(r *http.Request, deflt int64) (int64, error) {
	return QueryParamAsPositiveInt64OrDefault(r, QueryParamMaxDivergence, deflt)
}

func GetIncludeDirectoriesFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
	return QueryParamAsBoolOrDefault(r, QueryParamIncludeDirectories, deflt)
}

func GetFlattenDirectoriesFromQueryOrDefault(r *http.Request, deflt bool) (bool, error) {
	return QueryParamAsBoolOrDefault(r, QueryParamFlattenDirectories, deflt)
}

func GetCommitSHAFromPath(r *http.Request) (string, error) {
	return PathParamOrError(r, PathParamCommitSHA)
}

// ParseSortBranch extracts the branch sort parameter from the url.
func ParseSortBranch(r *http.Request) enum.BranchSortOption {
	return enum.ParseBranchSortOption(
		r.URL.Query().Get(QueryParamSort),
	)
}

func ParseBranchMetadataOptions(r *http.Request) (types.BranchMetadataOptions, error) {
	includeChecks, err := GetIncludeChecksFromQueryOrDefault(r, false)
	if err != nil {
		return types.BranchMetadataOptions{}, err
	}

	includeRules, err := GetIncludeRulesFromQueryOrDefault(r, false)
	if err != nil {
		return types.BranchMetadataOptions{}, err
	}

	includePullReqs, err := GetIncludePullReqsFromQueryOrDefault(r, false)
	if err != nil {
		return types.BranchMetadataOptions{}, err
	}

	maxDivergence, err := GetMaxDivergenceFromQueryOrDefault(r, 0)
	if err != nil {
		return types.BranchMetadataOptions{}, err
	}

	return types.BranchMetadataOptions{
		IncludeChecks:   includeChecks,
		IncludeRules:    includeRules,
		IncludePullReqs: includePullReqs,
		MaxDivergence:   int(maxDivergence),
	}, nil
}

// ParseBranchFilter extracts the branch filter from the url.
func ParseBranchFilter(r *http.Request) (*types.BranchFilter, error) {
	includeCommit, err := GetIncludeCommitFromQueryOrDefault(r, false)
	if err != nil {
		return nil, err
	}

	metadataOptions, err := ParseBranchMetadataOptions(r)
	if err != nil {
		return nil, err
	}

	return &types.BranchFilter{
		Query:                 ParseQuery(r),
		Sort:                  ParseSortBranch(r),
		Order:                 ParseOrder(r),
		Page:                  ParsePage(r),
		Size:                  ParseLimit(r),
		IncludeCommit:         includeCommit,
		BranchMetadataOptions: metadataOptions,
	}, nil
}

// ParseSortTag extracts the tag sort parameter from the url.
func ParseSortTag(r *http.Request) enum.TagSortOption {
	return enum.ParseTagSortOption(
		r.URL.Query().Get(QueryParamSort),
	)
}

// ParseTagFilter extracts the tag filter from the url.
func ParseTagFilter(r *http.Request) *types.TagFilter {
	return &types.TagFilter{
		Query: ParseQuery(r),
		Sort:  ParseSortTag(r),
		Order: ParseOrder(r),
		Page:  ParsePage(r),
		Size:  ParseLimit(r),
	}
}

// ParseCommitFilter extracts the commit filter from the url.
func ParseCommitFilter(r *http.Request) (*types.CommitFilter, error) {
	// since is optional, skipped if set to 0
	since, err := QueryParamAsPositiveInt64OrDefault(r, QueryParamSince, 0)
	if err != nil {
		return nil, err
	}
	// until is optional, skipped if set to 0
	until, err := QueryParamAsPositiveInt64OrDefault(r, QueryParamUntil, 0)
	if err != nil {
		return nil, err
	}
	includeStats, err := QueryParamAsBoolOrDefault(r, QueryParamIncludeStats, false)
	if err != nil {
		return nil, err
	}

	commiterIDs, err := QueryParamListAsPositiveInt64(r, QueryParamCommitterID)
	if err != nil {
		return nil, err
	}

	authorIDs, err := QueryParamListAsPositiveInt64(r, QueryParamAuthorID)
	if err != nil {
		return nil, err
	}

	return &types.CommitFilter{
		After: QueryParamOrDefault(r, QueryParamAfter, ""),
		PaginationFilter: types.PaginationFilter{
			Page:  ParsePage(r),
			Limit: ParseLimit(r),
		},
		Path:         QueryParamOrDefault(r, QueryParamPath, ""),
		Since:        since,
		Until:        until,
		Committer:    QueryParamOrDefault(r, QueryParamCommitter, ""),
		CommitterIDs: commiterIDs,
		Author:       QueryParamOrDefault(r, QueryParamAuthor, ""),
		AuthorIDs:    authorIDs,
		IncludeStats: includeStats,
	}, nil
}

// GetGitProtocolFromHeadersOrDefault returns the git protocol from the request headers.
func GetGitProtocolFromHeadersOrDefault(r *http.Request, deflt string) string {
	return GetHeaderOrDefault(r, HeaderParamGitProtocol, deflt)
}

// GetGitServiceTypeFromQuery returns the git service type from the request query.
func GetGitServiceTypeFromQuery(r *http.Request) (enum.GitServiceType, error) {
	// git prefixes the service names with "git-" in the query
	const gitPrefix = "git-"

	val, err := QueryParamOrError(r, QueryParamService)
	if err != nil {
		return "", fmt.Errorf("failed to get param from query: %w", err)
	}
	if !strings.HasPrefix(val, gitPrefix) {
		return "", usererror.BadRequestf("not a git service type: %q", val)
	}

	return enum.ParseGitServiceType(val[len(gitPrefix):])
}

func GetFileDiffFromQuery(r *http.Request) (files gittypes.FileDiffRequests) {
	paths, _ := QueryParamList(r, "path")
	ranges, _ := QueryParamList(r, "range")

	for i, filepath := range paths {
		start := 0
		end := 0
		if i < len(ranges) {
			linesRange := ranges[i]
			parts := strings.Split(linesRange, ":")
			if len(parts) > 1 {
				end, _ = strconv.Atoi(parts[1])
			}
			if len(parts) > 0 {
				start, _ = strconv.Atoi(parts[0])
			}
		}
		files = append(files, gittypes.FileDiffRequest{
			Path:      filepath,
			StartLine: start,
			EndLine:   end,
		})
	}
	return
}

func GetCommitSHAFromQueryOrDefault(r *http.Request) string {
	return QueryParamOrDefault(r, QueryParamCommitSHA, "")
}
