package service

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

	"github.com/pingcap/agent-git-service/internal/db"
)

// ListRulesets retrieves all rulesets for a given repository.
func (s *Service) ListRulesets(ctx context.Context, repoFullName string) ([]db.Ruleset, error) {
	repo, err := s.GetRepo(ctx, repoFullName)
	if err != nil {
		return nil, err
	}

	var rulesets []db.Ruleset
	if err := s.DBForCtx(ctx).Where("repository_id = ?", repo.ID).Find(&rulesets).Error; err != nil {
		return nil, err
	}
	return rulesets, nil
}

// CreateRuleset creates a ruleset for a repository.
func (s *Service) CreateRuleset(ctx context.Context, repoFullName string, rs *db.Ruleset) error {
	repo, err := s.GetRepo(ctx, repoFullName)
	if err != nil {
		return err
	}
	if err := s.requireRepoPermission(ctx, repo.ID, RepoPermissionAdmin); err != nil {
		return err
	}
	if err := validateRulesetRules(rs.RulesJSON); err != nil {
		return err
	}
	rs.RepositoryID = repo.ID
	return s.DBForCtx(ctx).Create(rs).Error
}

func validateRulesetRules(raw string) error {
	if strings.TrimSpace(raw) == "" {
		return nil
	}
	var rules []struct {
		Type       string          `json:"type"`
		Parameters json.RawMessage `json:"parameters"`
	}
	if err := json.Unmarshal([]byte(raw), &rules); err != nil {
		return fmt.Errorf("%w: rules must be a JSON array", ErrValidation)
	}
	for _, rule := range rules {
		if rule.Type != "merge_queue" {
			continue
		}
		var p MergeQueueConfiguration
		decoder := json.NewDecoder(bytes.NewReader(rule.Parameters))
		decoder.DisallowUnknownFields()
		if err := decoder.Decode(&p); err != nil {
			return fmt.Errorf("%w: invalid merge_queue parameters: %v", ErrValidation, err)
		}
		var fields map[string]json.RawMessage
		if err := json.Unmarshal(rule.Parameters, &fields); err != nil {
			return fmt.Errorf("%w: merge_queue parameters must be an object", ErrValidation)
		}
		for _, name := range []string{
			"check_response_timeout_minutes", "grouping_strategy", "merge_method",
			"max_entries_to_build", "max_entries_to_merge", "min_entries_to_merge",
			"min_entries_to_merge_wait_minutes",
		} {
			if _, ok := fields[name]; !ok {
				return fmt.Errorf("%w: %s is required", ErrValidation, name)
			}
		}
		if p.CheckResponseTimeoutMinutes < 1 || p.CheckResponseTimeoutMinutes > 360 {
			return fmt.Errorf("%w: check_response_timeout_minutes must be between 1 and 360", ErrValidation)
		}
		if p.GroupingStrategy != "ALLGREEN" && p.GroupingStrategy != "HEADGREEN" {
			return fmt.Errorf("%w: grouping_strategy must be ALLGREEN or HEADGREEN", ErrValidation)
		}
		if p.MergeMethod != "MERGE" && p.MergeMethod != "SQUASH" && p.MergeMethod != "REBASE" {
			return fmt.Errorf("%w: merge_method must be MERGE, SQUASH, or REBASE", ErrValidation)
		}
		for name, value := range map[string]int{
			"max_entries_to_build": p.MaxEntriesToBuild,
			"max_entries_to_merge": p.MaxEntriesToMerge,
			"min_entries_to_merge": p.MinEntriesToMerge,
		} {
			if value < 0 || value > 100 {
				return fmt.Errorf("%w: %s must be between 0 and 100", ErrValidation, name)
			}
		}
		if p.MinEntriesToMerge > p.MaxEntriesToMerge {
			return fmt.Errorf("%w: min_entries_to_merge cannot exceed max_entries_to_merge", ErrValidation)
		}
		if p.MinEntriesToMergeWaitMinutes < 0 || p.MinEntriesToMergeWaitMinutes > 360 {
			return fmt.Errorf("%w: min_entries_to_merge_wait_minutes must be between 0 and 360", ErrValidation)
		}
	}
	return nil
}

// GetRuleset retrieves a single ruleset by ID.
func (s *Service) GetRuleset(ctx context.Context, id uint) (db.Ruleset, error) {
	var rs db.Ruleset
	if err := s.DBForCtx(ctx).First(&rs, id).Error; err != nil {
		return rs, wrapErr(err)
	}
	return rs, nil
}

// ListBranchRulesets retrieves branch rulesets for a given repository.
func (s *Service) ListBranchRulesets(ctx context.Context, repoFullName string) ([]db.Ruleset, error) {
	repo, err := s.GetRepo(ctx, repoFullName)
	if err != nil {
		return nil, err
	}

	var rulesets []db.Ruleset
	if err := s.DBForCtx(ctx).Where("repository_id = ? AND target = 'branch'", repo.ID).Find(&rulesets).Error; err != nil {
		return nil, err
	}
	return rulesets, nil
}
