package accesslog

import (
	"github.com/yusing/go-proxy/internal/gperr"
	"github.com/yusing/go-proxy/internal/utils"
)

type (
	ConfigBase struct {
		BufferSize int        `json:"buffer_size"`
		Path       string     `json:"path"`
		Stdout     bool       `json:"stdout"`
		Retention  *Retention `json:"retention" aliases:"keep"`
	}
	ACLLoggerConfig struct {
		ConfigBase
		LogAllowed bool `json:"log_allowed"`
	}
	RequestLoggerConfig struct {
		ConfigBase
		Format  Format  `json:"format" validate:"oneof=common combined json"`
		Filters Filters `json:"filters"`
		Fields  Fields  `json:"fields"`
	}
	Config struct {
		*ConfigBase
		acl *ACLLoggerConfig
		req *RequestLoggerConfig
	}
	AnyConfig interface {
		ToConfig() *Config
		IO() (WriterWithName, error)
	}

	Format  string
	Filters struct {
		StatusCodes LogFilter[*StatusCodeRange] `json:"status_codes"`
		Method      LogFilter[HTTPMethod]       `json:"method"`
		Host        LogFilter[Host]             `json:"host"`
		Headers     LogFilter[*HTTPHeader]      `json:"headers"` // header exists or header == value
		CIDR        LogFilter[*CIDR]            `json:"cidr"`
	}
	Fields struct {
		Headers FieldConfig `json:"headers"`
		Query   FieldConfig `json:"query"`
		Cookies FieldConfig `json:"cookies"`
	}
)

var (
	FormatCommon   Format = "common"
	FormatCombined Format = "combined"
	FormatJSON     Format = "json"

	ReqLoggerFormats = []Format{FormatCommon, FormatCombined, FormatJSON}
)

const DefaultBufferSize = 64 * kilobyte // 64KB

func (cfg *ConfigBase) Validate() gperr.Error {
	if cfg.Path == "" && !cfg.Stdout {
		return gperr.New("path or stdout is required")
	}
	return nil
}

func (cfg *ConfigBase) IO() (WriterWithName, error) {
	ios := make([]WriterWithName, 0, 2)
	if cfg.Stdout {
		ios = append(ios, stdoutIO)
	}
	if cfg.Path != "" {
		io, err := newFileIO(cfg.Path)
		if err != nil {
			return nil, err
		}
		ios = append(ios, io)
	}
	if len(ios) == 0 {
		return nil, nil
	}
	return NewMultiWriter(ios...), nil
}

func (cfg *ACLLoggerConfig) ToConfig() *Config {
	return &Config{
		ConfigBase: &cfg.ConfigBase,
		acl:        cfg,
	}
}

func (cfg *RequestLoggerConfig) ToConfig() *Config {
	return &Config{
		ConfigBase: &cfg.ConfigBase,
		req:        cfg,
	}
}

func DefaultRequestLoggerConfig() *RequestLoggerConfig {
	return &RequestLoggerConfig{
		ConfigBase: ConfigBase{
			BufferSize: DefaultBufferSize,
			Retention:  &Retention{Days: 30},
		},
		Format: FormatCombined,
		Fields: Fields{
			Headers: FieldConfig{
				Default: FieldModeDrop,
			},
			Query: FieldConfig{
				Default: FieldModeKeep,
			},
			Cookies: FieldConfig{
				Default: FieldModeDrop,
			},
		},
	}
}

func DefaultACLLoggerConfig() *ACLLoggerConfig {
	return &ACLLoggerConfig{
		ConfigBase: ConfigBase{
			BufferSize: DefaultBufferSize,
			Retention:  &Retention{Days: 30},
		},
	}
}

func init() {
	utils.RegisterDefaultValueFactory(DefaultRequestLoggerConfig)
	utils.RegisterDefaultValueFactory(DefaultACLLoggerConfig)
}
