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

import (
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/harness/gitness/errors"
	"github.com/harness/gitness/types/enum"
)

const (
	maxLabelLength = 50
)

type Label struct {
	ID          int64           `json:"id"`
	SpaceID     *int64          `json:"space_id,omitempty"`
	RepoID      *int64          `json:"repo_id,omitempty"`
	Scope       int64           `json:"scope"`
	Key         string          `json:"key"`
	Description string          `json:"description"`
	Type        enum.LabelType  `json:"type"`
	Color       enum.LabelColor `json:"color"`
	ValueCount  int64           `json:"value_count"`
	Created     int64           `json:"created"`
	Updated     int64           `json:"updated"`
	CreatedBy   int64           `json:"created_by"`
	UpdatedBy   int64           `json:"updated_by"`
}

type LabelValue struct {
	ID        int64           `json:"id"`
	LabelID   int64           `json:"label_id"`
	Value     string          `json:"value"`
	Color     enum.LabelColor `json:"color"`
	Created   int64           `json:"created"`
	Updated   int64           `json:"updated"`
	CreatedBy int64           `json:"created_by"`
	UpdatedBy int64           `json:"updated_by"`
}

type LabelWithValues struct {
	Label  `json:"label"`
	Values []*LabelValue `json:"values"`
}

// Used to assign label to pullreq.
type PullReqLabel struct {
	PullReqID int64  `json:"pullreq_id"`
	LabelID   int64  `json:"label_id"`
	ValueID   *int64 `json:"value_id,omitempty"`
	Created   int64  `json:"created"`
	Updated   int64  `json:"updated"`
	CreatedBy int64  `json:"created_by"`
	UpdatedBy int64  `json:"updated_by"`
}

type LabelInfo struct {
	SpaceID  *int64          `json:"-"`
	RepoID   *int64          `json:"-"`
	Scope    int64           `json:"scope"`
	ID       int64           `json:"id"`
	Type     enum.LabelType  `json:"type"`
	Key      string          `json:"key"`
	Color    enum.LabelColor `json:"color"`
	Assigned *bool           `json:"assigned,omitempty"`
}

type LabelValueInfo struct {
	LabelID *int64  `json:"-"`
	ID      *int64  `json:"id,omitempty"`
	Value   *string `json:"value,omitempty"`
	Color   *string `json:"color,omitempty"`
}

type LabelAssignment struct {
	LabelInfo
	AssignedValue *LabelValueInfo   `json:"assigned_value,omitempty"`
	Values        []*LabelValueInfo `json:"values,omitempty"` // query param ?assignable=true
}

type LabelPullReqAssignmentInfo struct {
	PullReqID  int64            `json:"-"`
	LabelID    int64            `json:"id"`
	LabelKey   string           `json:"key"`
	LabelColor enum.LabelColor  `json:"color,omitempty"`
	LabelScope int64            `json:"scope"`
	ValueCount int64            `json:"value_count"`
	ValueID    *int64           `json:"value_id,omitempty"`
	Value      *string          `json:"value,omitempty"`
	ValueColor *enum.LabelColor `json:"value_color,omitempty"`
}

type ScopeData struct {
	// Scope = 0 is repo, scope >= 1 is a depth level of a space
	Scope int64       `json:"scope"`
	Space *Space      `json:"space,omitempty"`
	Repo  *Repository `json:"repository,omitempty"`
}

// Used to fetch label and values from a repo and space hierarchy.
type ScopesLabels struct {
	ScopeData []*ScopeData       `json:"scope_data"`
	LabelData []*LabelAssignment `json:"label_data"`
}

// LabelFilter stores label query parameters.
type AssignableLabelFilter struct {
	ListQueryFilter
	Assignable bool `json:"assignable,omitempty"`
}
type LabelFilter struct {
	ListQueryFilter
	Inherited bool `json:"inherited,omitempty"`
}

type DefineLabelInput struct {
	Key         string          `json:"key"`
	Type        enum.LabelType  `json:"type"`
	Description string          `json:"description"`
	Color       enum.LabelColor `json:"color"`
}

func (in *DefineLabelInput) Sanitize() error {
	if err := sanitizeLabelText(&in.Key, "key"); err != nil {
		return err
	}

	sanitizeDescription(&in.Description)

	if err := sanitizeLabelType(&in.Type); err != nil {
		return err
	}

	if err := sanitizeLabelColor(&in.Color); err != nil {
		return err
	}

	return nil
}

type UpdateLabelInput struct {
	Key         *string          `json:"key,omitempty"`
	Type        *enum.LabelType  `json:"type,omitempty"`
	Description *string          `json:"description,omitempty"`
	Color       *enum.LabelColor `json:"color,omitempty"`
}

func (in *UpdateLabelInput) Sanitize() error {
	if err := sanitizeLabelText(in.Key, "key"); err != nil {
		return err
	}

	sanitizeDescription(in.Description)

	if err := sanitizeLabelType(in.Type); err != nil {
		return err
	}

	if err := sanitizeLabelColor(in.Color); err != nil {
		return err
	}

	return nil
}

type DefineValueInput struct {
	Value string          `json:"value"`
	Color enum.LabelColor `json:"color"`
}

func (in *DefineValueInput) Sanitize() error {
	if err := sanitizeLabelText(&in.Value, "value"); err != nil {
		return err
	}

	if err := sanitizeLabelColor(&in.Color); err != nil {
		return err
	}

	return nil
}

type UpdateValueInput struct {
	Value *string          `json:"value"`
	Color *enum.LabelColor `json:"color"`
}

func (in *UpdateValueInput) Sanitize() error {
	if in.Value != nil {
		if err := sanitizeLabelText(in.Value, "value"); err != nil {
			return err
		}
	}

	if err := sanitizeLabelColor(in.Color); err != nil {
		return err
	}

	return nil
}

type PullReqCreateInput struct {
	LabelID int64  `json:"label_id"`
	ValueID *int64 `json:"value_id"`
	Value   string `json:"value"`
}

type PullReqUpdateInput struct {
	LabelValueID *int64 `json:"label_value_id,omitempty"`
}

func (in PullReqCreateInput) Validate() error {
	if (in.ValueID != nil && *in.ValueID > 0) && in.Value != "" {
		return errors.InvalidArgument("cannot accept both value id and value")
	}
	return nil
}

type SaveLabelInput struct {
	ID int64 `json:"id"`
	DefineLabelInput
}
type SaveLabelValueInput struct {
	ID int64 `json:"id"`
	DefineValueInput
}

type SaveInput struct {
	Label  SaveLabelInput         `json:"label"`
	Values []*SaveLabelValueInput `json:"values,omitempty"`
}

func (in *SaveInput) Sanitize() error {
	if err := in.Label.Sanitize(); err != nil {
		return err
	}

	for _, value := range in.Values {
		if err := value.Sanitize(); err != nil {
			return err
		}
	}

	return nil
}

func sanitizeLabelText(text *string, typ string) error {
	if text == nil {
		return nil
	}

	*text = strings.TrimSpace(*text)

	if len(*text) == 0 {
		return errors.InvalidArgument("%s must be a non-empty string", typ)
	}

	if utf8.RuneCountInString(*text) > maxLabelLength {
		return errors.InvalidArgument("%s can have at most %d characters", typ, maxLabelLength)
	}

	for _, ch := range *text {
		if unicode.IsControl(ch) {
			return errors.InvalidArgument("%s cannot contain control characters", typ)
		}
	}

	return nil
}

func sanitizeDescription(description *string) {
	if description == nil {
		return
	}

	*description = strings.TrimSpace(*description)
}

func sanitizeLabelType(typ *enum.LabelType) error {
	if typ == nil {
		return nil
	}

	*typ = enum.LabelType(trimLowerText(string(*typ)))

	var ok bool
	if *typ, ok = typ.Sanitize(); !ok {
		return errors.InvalidArgument("invalid label type")
	}

	return nil
}

func sanitizeLabelColor(color *enum.LabelColor) error {
	if color == nil {
		return nil
	}

	*color = enum.LabelColor(trimLowerText(string(*color)))

	var ok bool
	if *color, ok = color.Sanitize(); !ok {
		return errors.InvalidArgument("invalid label color")
	}

	return nil
}

func trimLowerText(text string) string {
	return strings.ToLower(strings.TrimSpace(text))
}
