package dialectsfcrm

import (
	"context"
	"fmt"
	"strings"

	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
)

var fieldHistorySelectFields = []string{
	"Id", "Field", "OldValue", "NewValue", "DataType", "CreatedDate", "CreatedById",
}

// FieldHistoryReportSourceRegistrations returns the Salesforce-owned report
// source shapes backed by the neutral audit HistoryQuery contract.
func FieldHistoryReportSourceRegistrations(query contract.Query) ([]contract.VirtualReportSourceRegistration, error) {
	return FieldHistoryReportSourceRegistrationsWithOptions(query, FieldHistoryReportSourceOptions{})
}

// FieldHistoryReportSourceOptions carries product-owned report columns supplied
// by a consuming pack without teaching the official CRM pack tenant field names.
type FieldHistoryReportSourceOptions struct {
	OpportunityHistoryColumns []OpportunityHistoryReportColumn `json:"opportunityHistoryColumns,omitempty"`
}

// OpportunityHistoryReportColumn maps one report column to an Opportunity
// field. HistoricalSnapshot selects point-in-time reconstruction; otherwise the
// current parent-record value is used.
type OpportunityHistoryReportColumn struct {
	Name               string `json:"name"`
	FieldAPIName       string `json:"fieldAPIName"`
	HistoricalSnapshot bool   `json:"historicalSnapshot,omitempty"`
}

// FieldHistoryReportSourceRegistrationsWithOptions returns the Salesforce-owned
// source shapes plus validated consuming-pack Opportunity History columns.
func FieldHistoryReportSourceRegistrationsWithOptions(query contract.Query, options FieldHistoryReportSourceOptions) ([]contract.VirtualReportSourceRegistration, error) {
	if query == nil {
		return nil, fmt.Errorf("dialect-sf-crm: field-history report source requires query")
	}
	opportunityHistory, err := newOpportunityHistoryReportSourceRegistrationWithColumns(query, options.OpportunityHistoryColumns)
	if err != nil {
		return nil, err
	}
	return []contract.VirtualReportSourceRegistration{
		newFieldHistoryReportSourceRegistration("AccountHistory", "Account", "AccountId", "AccountName", query),
		newFieldHistoryReportSourceRegistration("OpportunityFieldHistory", "Opportunity", "OpportunityId", "OpportunityName", query),
		opportunityHistory,
	}, nil
}

func newFieldHistoryReportSourceRegistration(sourceRef, parentObject, parentIDColumn, parentNameColumn string, query contract.Query) contract.VirtualReportSourceRegistration {
	return contract.VirtualReportSourceRegistration{
		SourceRef:                sourceRef,
		ParentObjectAPIName:      parentObject,
		RowFieldSecurityRequired: true,
		Capabilities: contract.VirtualReportSourceCapabilities{
			Filters:    true,
			Grouping:   true,
			Pagination: true,
		},
		Columns: []contract.VirtualReportColumn{
			{Name: "Id", MetadataOnly: true},
			{Name: parentIDColumn, MetadataOnly: true},
			{Name: parentNameColumn, FieldReferences: []contract.VirtualReportFieldReference{{ObjectAPIName: parentObject, FieldAPIName: "Name"}}},
			{Name: "Field"},
			{Name: "OldValue"},
			{Name: "NewValue"},
			{Name: "DataType"},
			{Name: "CreatedDate"},
			{Name: "CreatedById"},
		},
		Source: &fieldHistoryReportSource{
			query:            query,
			parentObject:     parentObject,
			parentIDColumn:   parentIDColumn,
			parentNameColumn: parentNameColumn,
		},
	}
}

type fieldHistoryReportSource struct {
	query            contract.Query
	parentObject     string
	parentIDColumn   string
	parentNameColumn string
}

func (s *fieldHistoryReportSource) Execute(ctx context.Context, request contract.VirtualReportSourceRequest, user *models.UserSession) (*contract.VirtualReportSourceResult, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	parentID, filters, err := fieldHistoryReportFilters(request, s.parentIDColumn)
	if err != nil {
		return nil, err
	}
	result, err := s.query.QueryHistory(ctx, contract.HistoryQueryRequest{
		ParentObjectAPIName: s.parentObject,
		SelectFields:        fieldHistorySelectFields,
		ParentID:            parentID,
		Filter:              filters,
		SortDesc:            true,
		PageSize:            request.Limit,
		CursorOffset:        request.Offset,
	}, user)
	if err != nil {
		return nil, err
	}
	if result == nil {
		return &contract.VirtualReportSourceResult{}, nil
	}
	if len(result.Rows) == 0 {
		return &contract.VirtualReportSourceResult{TotalCount: result.TotalSize}, nil
	}

	parents, err := s.loadParentRecords(ctx, result.Rows, request.Columns, user)
	if err != nil {
		return nil, err
	}
	rows := make([]contract.VirtualReportRow, 0, len(result.Rows))
	for _, history := range result.Rows {
		_, ok := parents[history.ParentID]
		if !ok {
			return nil, fmt.Errorf("dialect-sf-crm: history row %q has no visible parent record", history.ID)
		}
		values := make(map[string]contract.VirtualReportValue, len(request.Columns))
		for _, column := range request.Columns {
			value := contract.VirtualReportValue{}
			switch column.Name {
			case "Id":
				value.History = &contract.VirtualReportHistoryProjection{RecordID: history.ID, Field: contract.VirtualReportHistoryID}
			case s.parentIDColumn:
				value.AccessObjectAPIName = s.parentObject
				value.RecordFieldName = "Id"
			case s.parentNameColumn:
				value.AccessObjectAPIName = s.parentObject
				value.RecordFieldName = "Name"
			case "Field":
				value.History = &contract.VirtualReportHistoryProjection{RecordID: history.ID, Field: contract.VirtualReportHistoryFieldName}
			case "OldValue":
				value.History = &contract.VirtualReportHistoryProjection{RecordID: history.ID, Field: contract.VirtualReportHistoryOldValue}
			case "NewValue":
				value.History = &contract.VirtualReportHistoryProjection{RecordID: history.ID, Field: contract.VirtualReportHistoryNewValue}
			case "DataType":
				value.History = &contract.VirtualReportHistoryProjection{RecordID: history.ID, Field: contract.VirtualReportHistoryDataType}
			case "CreatedDate":
				value.History = &contract.VirtualReportHistoryProjection{RecordID: history.ID, Field: contract.VirtualReportHistoryCreatedDate}
			case "CreatedById":
				value.History = &contract.VirtualReportHistoryProjection{RecordID: history.ID, Field: contract.VirtualReportHistoryCreatedByID}
			}
			values[column.Name] = value
		}
		rows = append(rows, contract.VirtualReportRow{
			Values:          values,
			HistoryRecordID: history.ID,
			AccessRecords: []contract.VirtualReportAccessRecord{{
				ObjectAPIName: s.parentObject,
				RecordID:      history.ParentID,
			}},
		})
	}
	return &contract.VirtualReportSourceResult{TotalCount: result.TotalSize, Rows: rows}, nil
}

func (s *fieldHistoryReportSource) loadParentRecords(ctx context.Context, rows []contract.HistoryRecord, columns []contract.VirtualReportColumn, user *models.UserSession) (map[string]contract.Record, error) {
	ids := make([]string, 0, len(rows))
	seen := make(map[string]struct{}, len(rows))
	for _, row := range rows {
		if _, ok := seen[row.ParentID]; ok {
			continue
		}
		seen[row.ParentID] = struct{}{}
		ids = append(ids, row.ParentID)
	}
	// OwnerId keeps this source's parent lookup on the normal RLS query path; it
	// is never copied into report values.
	selectFields := []string{"Id", "OwnerId"}
	for _, column := range columns {
		if column.Name == s.parentNameColumn {
			selectFields = append(selectFields, "Name")
			break
		}
	}
	result, err := s.query.Query(ctx, queryspec.QuerySpec{
		ObjectAPIName: s.parentObject,
		SelectFields:  selectFields,
		Filter:        queryspec.FieldPredicate("Id", "IN", ids),
		Limit:         len(ids),
	}, user)
	if err != nil {
		return nil, err
	}
	parents := make(map[string]contract.Record, len(ids))
	if result == nil {
		return parents, nil
	}
	for _, record := range result.Records {
		id := strings.TrimSpace(fmt.Sprint(record["Id"]))
		if id != "" {
			parents[id] = record
		}
	}
	return parents, nil
}

func fieldHistoryReportFilters(request contract.VirtualReportSourceRequest, parentIDColumn string) (string, queryspec.Predicate, error) {
	if strings.TrimSpace(request.Definition.FilterLogic) != "" {
		return "", queryspec.Predicate{}, fmt.Errorf("dialect-sf-crm: field-history reports do not support custom filter logic")
	}
	scope := strings.TrimSpace(request.Definition.Scope)
	if scope != "" && !strings.EqualFold(scope, "all") {
		return "", queryspec.Predicate{}, fmt.Errorf("dialect-sf-crm: field-history reports do not support scope %q", scope)
	}
	parentID := ""
	filters := make([]queryspec.Predicate, 0, len(request.Definition.Filters)+len(request.RuntimeFilters)+2)
	for _, filter := range request.Definition.Filters {
		if strings.EqualFold(filter.Column, parentIDColumn) {
			if parentID != "" || !isEqualsReportOperator(filter.Operator) {
				return "", queryspec.Predicate{}, fmt.Errorf("dialect-sf-crm: field-history parent filter requires one equality predicate")
			}
			parentID = filter.Value
			continue
		}
		lowered, err := lowerFieldHistoryReportFilter(filter.Column, filter.Operator, filter.Value)
		if err != nil {
			return "", queryspec.Predicate{}, err
		}
		filters = append(filters, lowered)
	}
	for column, value := range request.RuntimeFilters {
		if strings.EqualFold(column, parentIDColumn) {
			if parentID != "" {
				return "", queryspec.Predicate{}, fmt.Errorf("dialect-sf-crm: field-history parent filter requires one equality predicate")
			}
			parentID = fmt.Sprint(value)
			continue
		}
		lowered, err := lowerFieldHistoryReportFilter(column, "=", fmt.Sprint(value))
		if err != nil {
			return "", queryspec.Predicate{}, err
		}
		filters = append(filters, lowered)
	}
	if dateFilter := request.Definition.DateFilter; dateFilter != nil {
		if !strings.EqualFold(dateFilter.Column, "CreatedDate") {
			return "", queryspec.Predicate{}, fmt.Errorf("dialect-sf-crm: field-history date filter must target CreatedDate")
		}
		if dateFilter.StartDate != "" {
			filters = append(filters, queryspec.FieldPredicate("CreatedDate", ">=", dateFilter.StartDate))
		}
		if dateFilter.EndDate != "" {
			filters = append(filters, queryspec.FieldPredicate("CreatedDate", "<=", dateFilter.EndDate))
		}
	}
	return parentID, queryspec.And(filters...), nil
}

func lowerFieldHistoryReportFilter(column, operator, value string) (queryspec.Predicate, error) {
	switch strings.ToLower(strings.TrimSpace(column)) {
	case "id", "field", "createddate", "createdbyid":
	default:
		return queryspec.Predicate{}, fmt.Errorf("dialect-sf-crm: unsupported field-history report filter column %q", column)
	}
	switch strings.ToLower(strings.TrimSpace(operator)) {
	case "equals", "eq", "=":
		operator = "="
	case "notequal", "neq", "!=", "<>":
		operator = "!="
	case "greaterthan", "gt", ">":
		operator = ">"
	case "greaterorequal", "gte", ">=":
		operator = ">="
	case "lessthan", "lt", "<":
		operator = "<"
	case "lessorequal", "lte", "<=":
		operator = "<="
	case "contains":
		operator, value = "LIKE", "%"+value+"%"
	case "notcontain", "doesnotcontain":
		operator, value = "NOT LIKE", "%"+value+"%"
	case "startswith":
		operator, value = "LIKE", value+"%"
	default:
		return queryspec.Predicate{}, fmt.Errorf("dialect-sf-crm: unsupported field-history report filter operator for %q", column)
	}
	return queryspec.FieldPredicate(column, operator, value), nil
}

func isEqualsReportOperator(operator string) bool {
	switch strings.ToLower(strings.TrimSpace(operator)) {
	case "equals", "eq", "=":
		return true
	default:
		return false
	}
}
