//go:build mage_docs

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/google/jsonschema-go/jsonschema"

	"github.com/aquasecurity/trivy/pkg/flag"
)

// JSON Schema type constants
const (
	schemaTypeString  = "string"
	schemaTypeBoolean = "boolean"
	schemaTypeInteger = "integer"
	schemaTypeNumber  = "number"
	schemaTypeArray   = "array"
	schemaTypeObject  = "object"
)

const configSchemaPath = "schema/trivy-config.json"

// generateConfigSchema generates a JSON schema for trivy.yaml configuration file.
func generateConfigSchema(outputPath string, allFlagGroups []flag.FlagGroup) error {
	root := &jsonschema.Schema{
		Schema:      "https://json-schema.org/draft/2020-12/schema",
		Type:        schemaTypeObject,
		Title:       "Trivy Configuration",
		Description: "Configuration file for Trivy security scanner (trivy.yaml)",
		Properties:  make(map[string]*jsonschema.Schema),
	}

	for _, group := range allFlagGroups {
		for _, f := range group.Flags() {
			configName := f.GetConfigName()
			if configName == "" || f.Hidden() {
				continue
			}
			if err := addFlagToSchema(root, f); err != nil {
				return err
			}
		}
	}

	data, err := json.MarshalIndent(root, "", "  ")
	if err != nil {
		return err
	}

	// Ensure directory exists
	if err := os.MkdirAll("schema", 0755); err != nil {
		return err
	}

	return os.WriteFile(outputPath, data, 0644)
}

// addFlagToSchema adds a flag to the schema, creating nested objects as needed.
func addFlagToSchema(root *jsonschema.Schema, f flag.Flagger) error {
	configName := f.GetConfigName()
	parts := strings.Split(configName, ".")

	// Split into parent path and leaf name
	parentParts, leafName := parts[:len(parts)-1], parts[len(parts)-1]

	// Navigate/create intermediate objects
	current := root
	for _, part := range parentParts {
		if existing, ok := current.Properties[part]; ok {
			current = existing
		} else {
			newSchema := &jsonschema.Schema{
				Type:       schemaTypeObject,
				Properties: make(map[string]*jsonschema.Schema),
			}
			current.Properties[part] = newSchema
			current.PropertyOrder = append(current.PropertyOrder, part)
			current = newSchema
		}
	}

	// Add the leaf property
	schema, err := schemaFromFlag(f)
	if err != nil {
		return err
	}
	current.Properties[leafName] = schema
	current.PropertyOrder = append(current.PropertyOrder, leafName)
	return nil
}

// schemaFromFlag creates a JSON schema based on the flag's type, description, and allowed values.
func schemaFromFlag(f flag.Flagger) (*jsonschema.Schema, error) {
	schema, err := schemaFromFlagValue(f.GetDefaultValue())
	if err != nil {
		return nil, fmt.Errorf("flag %q: %w", f.GetConfigName(), err)
	}

	// Add description from Usage
	if usage := f.GetUsage(); usage != "" {
		schema.Description = usage
	}

	// Add enum if Values is set
	if values := f.GetValues(); len(values) > 0 {
		enumValues := make([]any, len(values))
		for i, v := range values {
			enumValues[i] = v
		}
		// For array types, enum should be in items, not at the array level
		if schema.Type == schemaTypeArray && schema.Items != nil {
			schema.Items.Enum = enumValues
		} else {
			schema.Enum = enumValues
		}
	}

	return schema, nil
}

// schemaFromFlagValue creates a JSON schema based on the flag's default value type.
func schemaFromFlagValue(val any) (*jsonschema.Schema, error) {
	switch val.(type) {
	case string:
		return &jsonschema.Schema{Type: schemaTypeString}, nil
	case bool:
		return &jsonschema.Schema{Type: schemaTypeBoolean}, nil
	case int:
		return &jsonschema.Schema{Type: schemaTypeInteger}, nil
	case float64:
		return &jsonschema.Schema{Type: schemaTypeNumber}, nil
	case []string:
		return &jsonschema.Schema{
			Type:  schemaTypeArray,
			Items: &jsonschema.Schema{Type: schemaTypeString},
		}, nil
	case time.Duration:
		return &jsonschema.Schema{Type: schemaTypeString}, nil
	case map[string][]string:
		return &jsonschema.Schema{
			Type: schemaTypeObject,
			AdditionalProperties: &jsonschema.Schema{
				Type:  schemaTypeArray,
				Items: &jsonschema.Schema{Type: schemaTypeString},
			},
		}, nil
	default:
		return nil, fmt.Errorf("unknown type %T, please update schemaFromFlagValue()", val)
	}
}
