package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
)

// section: code
// Custom error handling for JSON unmarshaling with detailed error information

// UnmarshalError provides detailed information about JSON unmarshaling failures
type UnmarshalError struct {
	Data      string
	FieldPath string
	Reason    string
	err       error
}

func (u *UnmarshalError) Error() string {
	return fmt.Sprintf("unmarshal error at %s: %s", u.FieldPath, u.Reason)
}

func (u *UnmarshalError) Unwrap() error {
	return u.err
}

// Config represents application configuration
type Config struct {
	Port     int      `json:"port"`
	Host     string   `json:"host"`
	Features Features `json:"features"`
}

type Features struct {
	Debug   bool `json:"debug"`
	Logging bool `json:"logging"`
}

// UnmarshalJSON implements custom unmarshaling with detailed error handling
func (c *Config) UnmarshalJSON(data []byte) error {
	// Use an alias to avoid infinite recursion
	type Alias Config
	aux := &struct {
		*Alias
	}{
		Alias: (*Alias)(c),
	}

	if err := json.Unmarshal(data, &aux); err != nil {
		// Extract field information from JSON error
		var syntaxErr *json.SyntaxError
		var unmarshalErr *json.UnmarshalTypeError

		switch {
		case errors.As(err, &syntaxErr):
			return &UnmarshalError{
				Data:      string(data),
				FieldPath: fmt.Sprintf("offset %d", syntaxErr.Offset),
				Reason:    syntaxErr.Error(),
				err:       err,
			}
		case errors.As(err, &unmarshalErr):
			return &UnmarshalError{
				Data:      string(data),
				FieldPath: unmarshalErr.Field,
				Reason:    fmt.Sprintf("expected %s but got %s", unmarshalErr.Type, unmarshalErr.Value),
				err:       err,
			}
		default:
			return &UnmarshalError{
				Data:      string(data),
				FieldPath: "unknown",
				Reason:    err.Error(),
				err:       err,
			}
		}
	}

	// Validate configuration
	if c.Port < 1 || c.Port > 65535 {
		return &UnmarshalError{
			Data:      string(data),
			FieldPath: "port",
			Reason:    fmt.Sprintf("port must be between 1 and 65535, got %d", c.Port),
			err:       errors.New("invalid port number"),
		}
	}

	if c.Host == "" {
		return &UnmarshalError{
			Data:      string(data),
			FieldPath: "host",
			Reason:    "host cannot be empty",
			err:       errors.New("missing required field"),
		}
	}

	return nil
}

// parseConfig attempts to parse JSON configuration with detailed error reporting
func parseConfig(jsonData string) (*Config, error) {
	var config Config
	err := json.Unmarshal([]byte(jsonData), &config)
	if err != nil {
		return nil, fmt.Errorf("failed to parse configuration: %w", err)
	}
	return &config, nil
}

// handleUnmarshalError demonstrates how to handle unmarshal errors
func handleUnmarshalError(err error) {
	var unmarshalErr *UnmarshalError
	if errors.As(err, &unmarshalErr) {
		fmt.Printf("Field: %s\n", unmarshalErr.FieldPath)
		fmt.Printf("Reason: %s\n", unmarshalErr.Reason)
		fmt.Printf("Data: %s\n", strings.TrimSpace(unmarshalErr.Data))

		// Check for underlying JSON errors
		var typeErr *json.UnmarshalTypeError
		if errors.As(err, &typeErr) {
			fmt.Printf("Type mismatch: field=%s, expected=%s, got=%s\n",
				typeErr.Field, typeErr.Type, typeErr.Value)
		}
	} else {
		fmt.Printf("Unexpected error: %v\n", err)
	}
}

func main() {
	// Example 1: Type mismatch error
	fmt.Println("=== Example 1: Type Mismatch ===")
	invalidTypeJSON := `{"port": "not-a-number", "host": "localhost", "features": {"debug": true, "logging": false}}`
	config, err := parseConfig(invalidTypeJSON)
	if err != nil {
		handleUnmarshalError(err)
	}

	fmt.Println("\n=== Example 2: Validation Error ===")
	invalidPortJSON := `{"port": 99999, "host": "localhost", "features": {"debug": true, "logging": false}}`
	config, err = parseConfig(invalidPortJSON)
	if err != nil {
		handleUnmarshalError(err)
	}

	fmt.Println("\n=== Example 3: Missing Required Field ===")
	missingFieldJSON := `{"port": 8080, "host": "", "features": {"debug": true, "logging": false}}`
	config, err = parseConfig(missingFieldJSON)
	if err != nil {
		handleUnmarshalError(err)
	}

	fmt.Println("\n=== Example 4: Valid Configuration ===")
	validJSON := `{"port": 8080, "host": "localhost", "features": {"debug": true, "logging": false}}`
	config, err = parseConfig(validJSON)
	if err != nil {
		handleUnmarshalError(err)
	} else {
		fmt.Printf("✓ Configuration loaded successfully: %+v\n", config)
	}
}

// section: code

/*
// section: output
=== Example 1: Type Mismatch ===
Field: port
Reason: expected int but got string
Data: {"port": "not-a-number", "host": "localhost", "features": {"debug": true, "logging": false}}
Type mismatch: field=port, expected=int, got=string

=== Example 2: Validation Error ===
Field: port
Reason: port must be between 1 and 65535, got 99999
Data: {"port": 99999, "host": "localhost", "features": {"debug": true, "logging": false}}

=== Example 3: Missing Required Field ===
Field: host
Reason: host cannot be empty
Data: {"port": 8080, "host": "", "features": {"debug": true, "logging": false}}

=== Example 4: Valid Configuration ===
✓ Configuration loaded successfully: &{Port:8080 Host:localhost Features:{Debug:true Logging:false}}
// section: output
*/
