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

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/harness/gitness/app/store"
	"github.com/harness/gitness/types"
)

type (
	Sanitizer interface {
		// Sanitize validates if the definition is valid and automatically corrects minor issues.
		Sanitize() error
	}

	Protection interface {
		MergeVerifier
		RefChangeVerifier

		UserIDs() ([]int64, error)
	}

	Definition interface {
		Sanitizer
		Protection
	}

	// DefinitionGenerator is the function that creates blank rules.
	DefinitionGenerator func() Definition

	// Manager is used to enforce protection rules.
	Manager struct {
		defGenMap map[types.RuleType]DefinitionGenerator
		ruleStore store.RuleStore
	}
)

var (
	ErrUnrecognizedType       = errors.New("unrecognized protection type")
	ErrAlreadyRegistered      = errors.New("protection type already registered")
	ErrPatternEmpty           = errors.New("name pattern can't be empty")
	ErrInvalidGlobstarPattern = errors.New("invalid globstar pattern")
)

func IsCritical(violations []types.RuleViolations) bool {
	for i := range violations {
		if violations[i].IsCritical() {
			return true
		}
	}
	return false
}

func IsBypassed(violations []types.RuleViolations) bool {
	for i := range violations {
		if violations[i].IsBypassed() {
			return true
		}
	}
	return false
}

// NewManager creates new protection Manager.
func NewManager(ruleStore store.RuleStore) *Manager {
	return &Manager{
		defGenMap: make(map[types.RuleType]DefinitionGenerator),
		ruleStore: ruleStore,
	}
}

// Register registers new types.RuleType.
func (m *Manager) Register(ruleType types.RuleType, gen DefinitionGenerator) error {
	_, ok := m.defGenMap[ruleType]
	if ok {
		return ErrAlreadyRegistered
	}

	m.defGenMap[ruleType] = gen

	return nil
}

func (m *Manager) FromJSON(ruleType types.RuleType, message json.RawMessage, strict bool) (Protection, error) {
	gen := m.defGenMap[ruleType]
	if gen == nil {
		return nil, ErrUnrecognizedType
	}

	decoder := json.NewDecoder(bytes.NewReader(message))

	if strict {
		decoder.DisallowUnknownFields()
	}

	r := gen()

	if err := decoder.Decode(&r); err != nil {
		return nil, err
	}

	if err := r.Sanitize(); err != nil {
		return nil, err
	}

	return r, nil
}

func (m *Manager) SanitizeJSON(ruleType types.RuleType, message json.RawMessage) (json.RawMessage, error) {
	r, err := m.FromJSON(ruleType, message, true)
	if err != nil {
		return nil, err
	}

	return ToJSON(r)
}

func (m *Manager) ForRepository(ctx context.Context, repoID int64) (Protection, error) {
	ruleInfos, err := m.ruleStore.ListAllRepoRules(ctx, repoID)
	if err != nil {
		return nil, fmt.Errorf("failed to list rules for repository: %w", err)
	}

	return ruleSet{
		rules:   ruleInfos,
		manager: m,
	}, nil
}
