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

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/harness/gitness/errors"

	"github.com/swaggest/jsonschema-go"
)

var (
	Nil  = Must("0000000000000000000000000000000000000000")
	None = SHA{}
	// regex defines the valid SHA format accepted by GIT (full form and short forms).
	// Note: as of now SHA is at most 40 characters long, but in the future it's moving to sha256
	// which is 64 chars - keep this forward-compatible.
	regex    = regexp.MustCompile("^[0-9a-f]{4,64}$")
	nilRegex = regexp.MustCompile("^0{4,64}$")

	// EmptyTree is the SHA of an empty tree.
	EmptyTree = Must("4b825dc642cb6eb9a060e54bf8d69288fbee4904")
)

// SHA represents a git sha.
type SHA struct {
	str string
}

// New creates and validates SHA from the value.
func New(value string) (SHA, error) {
	value = strings.TrimSpace(value)
	value = strings.ToLower(value)
	if !regex.MatchString(value) {
		return SHA{}, errors.InvalidArgument("the provided commit sha '%s' is of invalid format.", value)
	}
	return SHA{
		str: value,
	}, nil
}

// NewOrEmpty returns None if value is empty otherwise it will try to create
// and validate new SHA object.
func NewOrEmpty(value string) (SHA, error) {
	if value == "" {
		return None, nil
	}
	return New(value)
}

func (s SHA) GobEncode() ([]byte, error) {
	buffer := &bytes.Buffer{}
	err := gob.NewEncoder(buffer).Encode(s.str)
	if err != nil {
		return nil, fmt.Errorf("failed to pack sha value: %w", err)
	}
	return buffer.Bytes(), nil
}

func (s *SHA) GobDecode(v []byte) error {
	if err := gob.NewDecoder(bytes.NewReader(v)).Decode(&s.str); err != nil {
		return fmt.Errorf("failed to unpack sha value: %w", err)
	}
	return nil
}

func (s *SHA) UnmarshalJSON(content []byte) error {
	if s == nil {
		return nil
	}
	var str string
	err := json.Unmarshal(content, &str)
	if err != nil {
		return err
	}

	sha, err := NewOrEmpty(str)
	if err != nil {
		return err
	}
	s.str = sha.str
	return nil
}

func (s SHA) MarshalJSON() ([]byte, error) {
	return []byte("\"" + s.str + "\""), nil
}

// String returns string representation of the SHA.
func (s SHA) String() string {
	return s.str
}

// IsNil returns whether this SHA is all zeroes.
func (s SHA) IsNil() bool {
	return nilRegex.MatchString(s.str)
}

// IsEmpty returns whether this SHA is empty string.
func (s SHA) IsEmpty() bool {
	return s.str == ""
}

// Equal returns true if val has the same SHA.
func (s SHA) Equal(val SHA) bool {
	return s.str == val.str
}

// Must returns sha if there is an error it will panic.
func Must(value string) SHA {
	sha, err := New(value)
	if err != nil {
		panic("invalid SHA" + err.Error())
	}
	return sha
}

func (s SHA) JSONSchema() (jsonschema.Schema, error) {
	var schema jsonschema.Schema

	schema.AddType(jsonschema.String)
	schema.WithDescription("Git object hash")

	return schema, nil
}
