package datadog

import (
	"encoding/json"
	"fmt"
)

const (
	ArithmeticProcessorType    = "arithmetic-processor"
	AttributeRemapperType      = "attribute-remapper"
	CategoryProcessorType      = "category-processor"
	DateRemapperType           = "date-remapper"
	GeoIPParserType            = "geo-ip-parser"
	GrokParserType             = "grok-parser"
	LookupProcessorType        = "lookup-processor"
	MessageRemapperType        = "message-remapper"
	NestedPipelineType         = "pipeline"
	ServiceRemapperType        = "service-remapper"
	StatusRemapperType         = "status-remapper"
	StringBuilderProcessorType = "string-builder-processor"
	TraceIdRemapperType        = "trace-id-remapper"
	UrlParserType              = "url-parser"
	UserAgentParserType        = "user-agent-parser"
)

// LogsProcessor struct represents the processor object from Config API.
type LogsProcessor struct {
	Name       *string     `json:"name"`
	IsEnabled  *bool       `json:"is_enabled"`
	Type       *string     `json:"type"`
	Definition interface{} `json:"definition"`
}

// ArithmeticProcessor struct represents unique part of arithmetic processor
// object from config API.
type ArithmeticProcessor struct {
	Expression       *string `json:"expression"`
	Target           *string `json:"target"`
	IsReplaceMissing *bool   `json:"is_replace_missing"`
}

// AttributeRemapper struct represents unique part of attribute remapper object
// from config API.
type AttributeRemapper struct {
	Sources            []string `json:"sources"`
	SourceType         *string  `json:"source_type"`
	Target             *string  `json:"target"`
	TargetType         *string  `json:"target_type"`
	PreserveSource     *bool    `json:"preserve_source"`
	OverrideOnConflict *bool    `json:"override_on_conflict"`
}

// CategoryProcessor struct represents unique part of category processor object
// from config API.
type CategoryProcessor struct {
	Target     *string    `json:"target"`
	Categories []Category `json:"categories"`
}

// Category represents category object from config API.
type Category struct {
	Name   *string              `json:"name"`
	Filter *FilterConfiguration `json:"filter"`
}

// SourceRemapper represents the object from config API that contains
// only a list of sources.
type SourceRemapper struct {
	Sources []string `json:"sources"`
}

// GeoIPParser represents geoIpParser object from config API.
type GeoIPParser struct {
	Sources []string `json:"sources"`
	Target  *string  `json:"target"`
}

type StringBuilderProcessor struct {
	Template         *string `json:"template"`
	Target           *string `json:"target"`
	IsReplaceMissing *bool   `json:"is_replace_missing"`
}

// GrokParser represents the grok parser processor object from config API.
type GrokParser struct {
	Source   *string   `json:"source"`
	Samples  []string  `json:"samples"`
	GrokRule *GrokRule `json:"grok"`
}

// GrokRule represents the rules for grok parser from config API.
type GrokRule struct {
	SupportRules *string `json:"support_rules"`
	MatchRules   *string `json:"match_rules"`
}

// LookupProcessor represents the lookup processor from config API.
type LookupProcessor struct {
	Source        *string  `json:"source"`
	Target        *string  `json:"target"`
	LookupTable   []string `json:"lookup_table"`
	DefaultLookup *string  `json:"default_lookup,omitempty"`
}

// NestedPipeline represents the pipeline as processor from config API.
type NestedPipeline struct {
	Filter     *FilterConfiguration `json:"filter"`
	Processors []LogsProcessor      `json:"processors,omitempty"`
}

// UrlParser represents the url parser from config API.
type UrlParser struct {
	Sources                []string `json:"sources"`
	Target                 *string  `json:"target"`
	NormalizeEndingSlashes *bool    `json:"normalize_ending_slashes"`
}

// UserAgentParser represents the user agent parser from config API.
type UserAgentParser struct {
	Sources   []string `json:"sources"`
	Target    *string  `json:"target"`
	IsEncoded *bool    `json:"is_encoded"`
}

// buildProcessor converts processor Definition of type interface{} to a map of string and interface{}.
// Simple cast from interface{} to map[string]interface{} will not work for our case here,
// since the underlying types of Definition are the processor structs.
func buildProcessor(definition interface{}) (map[string]interface{}, error) {
	inrec, err := json.Marshal(definition)
	if err != nil {
		return nil, err
	}
	var processor map[string]interface{}
	if err = json.Unmarshal(inrec, &processor); err != nil {
		return nil, err
	}
	return processor, err
}

// MarshalJSON serializes logsprocessor struct to config API compatible json object.
func (processor *LogsProcessor) MarshalJSON() ([]byte, error) {
	mapProcessor, err := buildProcessor(processor.Definition)
	if err != nil {
		return nil, err
	}
	mapProcessor["name"] = processor.Name
	mapProcessor["is_enabled"] = processor.IsEnabled
	mapProcessor["type"] = processor.Type
	jsn, err := json.Marshal(mapProcessor)
	if err != nil {
		return nil, err
	}
	return jsn, err
}

// UnmarshalJSON deserializes the config API json object to LogsProcessor struct.
func (processor *LogsProcessor) UnmarshalJSON(data []byte) error {
	var processorHandler struct {
		Type      *string `json:"type"`
		Name      *string `json:"name"`
		IsEnabled *bool   `json:"is_enabled"`
	}
	if err := json.Unmarshal(data, &processorHandler); err != nil {
		return err
	}

	processor.Name = processorHandler.Name
	processor.IsEnabled = processorHandler.IsEnabled
	processor.Type = processorHandler.Type

	switch *processorHandler.Type {
	case ArithmeticProcessorType:
		var arithmeticProcessor ArithmeticProcessor
		if err := json.Unmarshal(data, &arithmeticProcessor); err != nil {
			return err
		}
		processor.Definition = arithmeticProcessor
	case AttributeRemapperType:
		var attributeRemapper AttributeRemapper
		if err := json.Unmarshal(data, &attributeRemapper); err != nil {
			return err
		}
		processor.Definition = attributeRemapper
	case CategoryProcessorType:
		var categoryProcessor CategoryProcessor
		if err := json.Unmarshal(data, &categoryProcessor); err != nil {
			return err
		}
		processor.Definition = categoryProcessor
	case DateRemapperType,
		MessageRemapperType,
		ServiceRemapperType,
		StatusRemapperType,
		TraceIdRemapperType:
		var sourceRemapper SourceRemapper
		if err := json.Unmarshal(data, &sourceRemapper); err != nil {
			return err
		}
		processor.Definition = sourceRemapper
	case GeoIPParserType:
		var geoIPParser GeoIPParser
		if err := json.Unmarshal(data, &geoIPParser); err != nil {
			return err
		}
		processor.Definition = geoIPParser
	case GrokParserType:
		var grokParser GrokParser
		if err := json.Unmarshal(data, &grokParser); err != nil {
			return err
		}
		processor.Definition = grokParser
	case LookupProcessorType:
		var lookupProcessor LookupProcessor
		if err := json.Unmarshal(data, &lookupProcessor); err != nil {
			return err
		}
		processor.Definition = lookupProcessor
	case NestedPipelineType:
		var nestedPipeline NestedPipeline
		if err := json.Unmarshal(data, &nestedPipeline); err != nil {
			return err
		}
		processor.Definition = nestedPipeline
	case StringBuilderProcessorType:
		var stringBuilder StringBuilderProcessor
		if err := json.Unmarshal(data, &stringBuilder); err != nil {
			return err
		}
		processor.Definition = stringBuilder
	case UrlParserType:
		var urlParser UrlParser
		if err := json.Unmarshal(data, &urlParser); err != nil {
			return err
		}
		processor.Definition = urlParser
	case UserAgentParserType:
		var userAgentParser UserAgentParser
		if err := json.Unmarshal(data, &userAgentParser); err != nil {
			return err
		}
		processor.Definition = userAgentParser
	default:
		return fmt.Errorf("cannot unmarshal processor of type: %s", *processorHandler.Type)
	}
	return nil
}
