package analytics

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

	pkgErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/logging"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

// NeutralReportDefinitionDialect accepts only the platform-neutral report shape:
// columns/grouping as string arrays and aggregates as structured objects. Product
// dialect shorthands such as Salesforce aggregate keys must be installed through
// ReportDefinitionDialect instead of being implicit in L0.
type NeutralReportDefinitionDialect struct{}

func (NeutralReportDefinitionDialect) ParseStoredDefinition(row contract.Record) (*models.ReportDefinition, error) {
	sourceRef := reportRecordString(row, "SourceRef")
	displayMode := reportRecordString(row, "DisplayMode")
	filterLogic := reportRecordString(row, "FilterLogic")

	def := &models.ReportDefinition{
		ID:           reportRecordString(row, "Id"),
		Name:         reportRecordString(row, "Name"),
		SourceObject: reportRecordString(row, "SourceObject"),
		SourceRef:    sourceRef,
		DisplayMode:  displayMode,
		FilterLogic:  filterLogic,
		Scope:        reportRecordString(row, "Scope"),
	}

	if colStr := getReportJSONString(row, "Columns"); colStr != "" {
		if err := json.Unmarshal([]byte(colStr), &def.Columns); err != nil {
			return nil, pkgErrors.NewInternalError(fmt.Sprintf("failed to parse columns (val=%s)", colStr), err)
		}
	}
	if filterStr := getReportJSONString(row, "Filters"); filterStr != "" {
		if err := parseNeutralFilters(filterStr, &def.Filters); err != nil {
			def.Filters = nil
		}
	}
	if groupStr := getReportJSONString(row, "Grouping"); groupStr != "" {
		if err := parseNeutralGrouping(groupStr, &def.Grouping); err != nil {
			return nil, pkgErrors.NewInternalError(fmt.Sprintf("failed to parse grouping (val=%s)", groupStr), err)
		}
	}
	if aggStr := getReportJSONString(row, "Aggregates"); aggStr != "" {
		if err := parseNeutralAggregates(aggStr, &def.Aggregates); err != nil {
			return nil, err
		}
	}
	dfStr := getReportJSONString(row, "DateFilter")
	if dfStr == "" {
		dfStr = getReportJSONString(row, "StandardDateFilter")
	}
	if dfStr != "" {
		if df, err := parseNeutralDateFilter(dfStr); err == nil && df.Column != "" {
			def.DateFilter = &df
		}
	}

	if def.DisplayMode == "" {
		if len(def.Grouping) > 0 {
			def.DisplayMode = "SUMMARY"
		} else {
			def.DisplayMode = "TABULAR"
		}
	}
	def.NormalizeBoundaryAliases()

	return def, nil
}

func (NeutralReportDefinitionDialect) ResolveSourceObject(sourceObject string, lookup contract.ReportSourceObjectLookup) string {
	return sourceObject
}

func (NeutralReportDefinitionDialect) NormalizeColumn(column string) string {
	return column
}

func (NeutralReportDefinitionDialect) SupplementalStoredDefinitionFields() []string {
	return nil
}

func (NeutralReportDefinitionDialect) BuildFilterExpression(_ contract.ReportFilterExpressionRequest) (contract.ReportFilterExpression, bool) {
	return contract.ReportFilterExpression{}, false
}

func effectiveReportDefinitionDialect(d contract.ReportDefinitionDialect) contract.ReportDefinitionDialect {
	if d == nil {
		return NeutralReportDefinitionDialect{}
	}
	return d
}

// WithReportDefinitionDialect installs an application report-definition parser
// and token normalizer. The default is NeutralReportDefinitionDialect.
func WithReportDefinitionDialect(d contract.ReportDefinitionDialect) ReportServiceOption {
	return func(s *ReportService) {
		s.reportDialect = effectiveReportDefinitionDialect(d)
	}
}

// SetReportDefinitionDialect is the boot-time mutation seam used by product
// composition hooks. It is not intended for per-request switching.
func (s *ReportService) SetReportDefinitionDialect(d contract.ReportDefinitionDialect) {
	if s == nil {
		return
	}
	s.reportDialect = effectiveReportDefinitionDialect(d)
}

func getReportJSONString(row contract.Record, key string) string {
	val, ok := row[key]
	if !ok || val == nil {
		return ""
	}
	if s, ok := val.(string); ok {
		return s
	}
	b, err := json.Marshal(val)
	if err != nil {
		logging.Default.Warn(fmt.Sprintf("getReportJSONString: json.Marshal failed for key %q (type %T): %v", key, val, err))
		return ""
	}
	return string(b)
}

func reportRecordString(row contract.Record, key string) string {
	if row == nil {
		return ""
	}
	value, ok := row[key]
	if !ok || value == nil {
		return ""
	}
	if s, ok := value.(string); ok {
		return s
	}
	return fmt.Sprintf("%v", value)
}

func parseNeutralGrouping(raw string, out *[]string) error {
	var simple []string
	if err := json.Unmarshal([]byte(raw), &simple); err != nil {
		return err
	}
	*out = simple
	return nil
}

type neutralStoredReportFilter struct {
	Column   string `json:"column"`
	Operator string `json:"operator"`
	Value    string `json:"value"`
}

type neutralStoredReportAggregate struct {
	Field string `json:"field"`
	Type  string `json:"type"`
}

type neutralStoredStandardDateFilter struct {
	Column        string `json:"column"`
	Preset        string `json:"preset"`
	DurationValue string `json:"durationValue"`
	StartDate     string `json:"startDate"`
	EndDate       string `json:"endDate"`
}

func parseNeutralFilters(raw string, out *[]models.ReportFilter) error {
	var structured []neutralStoredReportFilter
	if err := json.Unmarshal([]byte(raw), &structured); err != nil {
		return err
	}
	filters := make([]models.ReportFilter, len(structured))
	for i, filter := range structured {
		filters[i] = models.ReportFilter{
			Column:   filter.Column,
			Operator: filter.Operator,
			Value:    filter.Value,
		}
	}
	*out = filters
	return nil
}

func parseNeutralAggregates(raw string, out *[]models.ReportAggregate) error {
	var structured []neutralStoredReportAggregate
	if err := json.Unmarshal([]byte(raw), &structured); err == nil {
		if len(structured) == 0 || structured[0].Field != "" || structured[0].Type != "" {
			aggs := make([]models.ReportAggregate, len(structured))
			for i, agg := range structured {
				aggs[i] = models.ReportAggregate{Field: agg.Field, Type: agg.Type}
			}
			*out = aggs
			return nil
		}
	}

	var productKeys []string
	if err := json.Unmarshal([]byte(raw), &productKeys); err == nil {
		for _, key := range productKeys {
			if strings.TrimSpace(key) != "" {
				return pkgErrors.NewValidationError("reportMetadata.aggregates", "report definition dialect is required for product aggregate keys")
			}
		}
		*out = nil
		return nil
	}

	return pkgErrors.NewInternalError(fmt.Sprintf("failed to parse aggregates (val=%s)", raw), nil)
}

func parseNeutralDateFilter(raw string) (models.ReportDateFilter, error) {
	var dto neutralStoredStandardDateFilter
	if err := json.Unmarshal([]byte(raw), &dto); err != nil {
		return models.ReportDateFilter{}, err
	}
	out := models.ReportDateFilter{
		Column:        dto.Column,
		Preset:        dto.Preset,
		DurationValue: dto.DurationValue,
		StartDate:     dto.StartDate,
		EndDate:       dto.EndDate,
	}
	if out.Preset == "" {
		out.Preset = out.DurationValue
	}
	if out.DurationValue == "" {
		out.DurationValue = out.Preset
	}
	return out, nil
}
