package services

import (
	"context"
	"fmt"
	"strings"

	"github.com/nexuscrm/analytics"
	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/formulastorage"
)

func (sm *ServiceManager) MetadataMaintenanceContract() contract.MetadataMaintenance {
	if sm == nil {
		return nil
	}
	if sm.NeutralMetadataMaintenance != nil {
		return sm.NeutralMetadataMaintenance
	}
	if sm.AutoNumberMetadata == nil && sm.FormulaViewSvc == nil {
		return nil
	}
	return sm
}

func (sm *ServiceManager) RefreshAutoNumberMetadata(ctx context.Context) (*contract.AutoNumberMetadataRefreshResult, error) {
	if sm == nil || sm.AutoNumberMetadata == nil {
		return nil, &contract.MetadataMaintenanceError{
			Kind:    contract.MetadataMaintenanceErrorUnavailable,
			Message: "autonumber metadata source not initialized",
		}
	}
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		return nil, &contract.MetadataMaintenanceError{
			Kind:    contract.MetadataMaintenanceErrorInternal,
			Message: err.Error(),
			Cause:   err,
		}
	}

	reconciled := 0
	if eng := sm.AutoNumberEngine; eng != nil {
		eng.ReconcileQuarantine(ctx)
		if quarantine := eng.Quarantine(); quarantine != nil {
			reconciled = quarantine.Len()
		}
	}
	return &contract.AutoNumberMetadataRefreshResult{
		RuleCount:            len(sm.AutoNumberMetadata.ListRules()),
		QuarantinedAfterSync: reconciled,
		InProcess:            true,
	}, nil
}

func (sm *ServiceManager) FormulaViewStatus(ctx context.Context, objectName string) (*contract.FormulaViewStatusResult, error) {
	if err := sm.requireFormulaViewMaintenance(); err != nil {
		return nil, err
	}

	exists := sm.FormulaViewSvc.ViewExists(ctx, objectName)
	schema := sm.Metadata.GetSchema(ctx, objectName)
	activeCount := 0
	frozenCount := 0
	if schema != nil {
		for _, f := range schema.Fields {
			resolved, err := formulastorage.Resolve(formulastorage.SpecFromField(&f))
			if !resolved.FormulaBacked {
				continue
			}
			if resolved.RequiresFormulaView() {
				activeCount++
			} else {
				frozenCount++
			}
			_ = err
		}
	}

	return &contract.FormulaViewStatusResult{
		Object:             objectName,
		ViewExists:         exists,
		ViewName:           "v_" + objectName,
		ActiveFormulaCount: activeCount,
		FrozenFormulaCount: frozenCount,
		ResolvedTableName:  sm.FormulaViewSvc.ResolveTableName(ctx, schema),
	}, nil
}

func (sm *ServiceManager) PreviewFormulaViewDDL(ctx context.Context, objectName string) (*contract.FormulaViewDDLResult, error) {
	if err := sm.requireFormulaViewMaintenance(); err != nil {
		return nil, err
	}
	ddl, err := sm.FormulaViewSvc.PreviewDDL(ctx, objectName)
	if err != nil {
		return nil, sm.formulaViewMaintenanceError(err, "formula view DDL preview failed")
	}
	return &contract.FormulaViewDDLResult{Object: objectName, DDL: ddl}, nil
}

func (sm *ServiceManager) RebuildFormulaView(ctx context.Context, objectName string) (*contract.FormulaViewRebuildResult, error) {
	if err := sm.requireFormulaViewMaintenance(); err != nil {
		return nil, err
	}
	if sm.Metadata.GetSchema(ctx, objectName) == nil {
		return nil, &contract.MetadataMaintenanceError{
			Kind:    contract.MetadataMaintenanceErrorNotFound,
			Message: fmt.Sprintf("object %s not found", objectName),
		}
	}
	if err := sm.FormulaViewSvc.RebuildView(ctx, objectName); err != nil {
		return nil, sm.formulaViewMaintenanceError(err, "formula view rebuild failed")
	}
	return &contract.FormulaViewRebuildResult{Object: objectName, ViewName: "v_" + objectName}, nil
}

func (sm *ServiceManager) RebuildAllFormulaViews(ctx context.Context) (*contract.FormulaViewRebuildAllResult, error) {
	if err := sm.requireFormulaViewMaintenance(); err != nil {
		return nil, err
	}
	if err := sm.FormulaViewSvc.RebuildAllViews(ctx); err != nil {
		return nil, sm.formulaViewMaintenanceError(err, "formula view rebuild failed")
	}
	return &contract.FormulaViewRebuildAllResult{}, nil
}

func (sm *ServiceManager) ValidateFormulaView(ctx context.Context, req contract.FormulaViewValidationRequest) (*contract.FormulaViewValidationResult, error) {
	if err := sm.requireFormulaViewMaintenance(); err != nil {
		return nil, err
	}

	ast, err := analytics.Parse(req.Formula)
	if err != nil {
		return formulaViewInvalidResult(err), nil
	}
	schemaAwareValidated := false
	if err := sm.FormulaViewSvc.ValidateFormulaForField(ctx, req.ObjectName, req.FieldAPIName, req.Formula); err != nil {
		return formulaViewInvalidResult(err), nil
	}
	schemaAwareValidated = sm.Metadata.GetSchema(ctx, req.ObjectName) != nil

	referencedFields := analytics.ExtractFieldRefs(ast)
	crossRefs := analytics.ExtractCrossRefs(ast)
	crossRefStrings := make([]string, len(crossRefs))
	for i, cr := range crossRefs {
		crossRefStrings[i] = cr.Object + "." + cr.Field
	}

	compiled, err := sm.FormulaViewSvc.CompileFormulaPreview(ctx, req.ObjectName, req.Formula)
	if err != nil {
		if !schemaAwareValidated || !strings.Contains(err.Error(), "missing metadata join") {
			return formulaViewInvalidResult(err), nil
		}
	}

	return &contract.FormulaViewValidationResult{
		Valid:            true,
		CompiledSQL:      compiled,
		ReferencedFields: referencedFields,
		CrossTableRefs:   crossRefStrings,
	}, nil
}

func (sm *ServiceManager) requireFormulaViewMaintenance() error {
	if sm == nil || sm.FormulaViewSvc == nil || sm.Metadata == nil {
		return &contract.MetadataMaintenanceError{
			Kind:    contract.MetadataMaintenanceErrorUnavailable,
			Message: "formula view service is not configured",
		}
	}
	return nil
}

func (sm *ServiceManager) formulaViewMaintenanceError(err error, message string) error {
	if err == nil {
		return nil
	}
	if apperrors.IsNotFound(err) {
		return &contract.MetadataMaintenanceError{
			Kind:    contract.MetadataMaintenanceErrorNotFound,
			Message: err.Error(),
			Cause:   err,
		}
	}
	return &contract.MetadataMaintenanceError{
		Kind:    contract.MetadataMaintenanceErrorInternal,
		Message: message,
		Cause:   err,
	}
}

func formulaViewInvalidResult(err error) *contract.FormulaViewValidationResult {
	message := ""
	if err != nil {
		message = err.Error()
	}
	return &contract.FormulaViewValidationResult{Valid: false, Error: message}
}
