package dialectsfcrm

import (
	"context"
	"fmt"
	"net/http"
	"strings"

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

func describeHistoryObject(ctx context.Context, req *dialect.Request, metadata contract.Metadata, policy contract.Policy, historyObjectName string) *dialect.Response {
	parentObjectName := parentObjectFromHistoryName(historyObjectName)
	access, resp := effectiveAccessForDescribe(ctx, policy, req.User, parentObjectName, "Insufficient access to describe this object", "Unable to resolve effective access")
	if resp != nil {
		return resp
	}
	parent, err := metadata.Describe(ctx, parentObjectName)
	if err != nil || parent == nil || !historySchemaSupportsFieldHistory(parent) {
		return notFoundResponse("The requested resource does not exist")
	}
	return &dialect.Response{Status: http.StatusOK, Body: describeHistoryObjectResponse(req.Version, historyObjectName, parent, access)}
}

func historySchemaSupportsFieldHistory(schema *models.ObjectMetadata) bool {
	if schema == nil || schema.APIName == opportunityHistoryObject || isHistoryObject(schema.APIName) || !fieldHistoryParentSupportsFieldHistory(schema.APIName) {
		return false
	}
	for _, field := range schema.Fields {
		if strings.TrimSpace(field.APIName) != "" {
			return true
		}
	}
	return false
}

func historyObjectNameForParent(parentObjectName string) string {
	if parentObjectName == "Opportunity" {
		return opportunityFieldHistoryObject
	}
	if strings.HasSuffix(parentObjectName, "__c") {
		return strings.TrimSuffix(parentObjectName, "__c") + "__History"
	}
	return parentObjectName + historySuffix
}

func describeHistoryGlobalObject(version string, parent *models.ObjectMetadata) describeGlobalObject {
	historyObjectName := historyObjectNameForParent(parent.APIName)
	historyLabel := parent.Label + " History"
	if historyObjectName == opportunityFieldHistoryObject {
		historyLabel = parent.Label + " Field History"
	}
	sobjectsBase := versionedSObjectsBasePath(version)
	return describeGlobalObject{
		Createable:          false,
		Custom:              parent.IsCustom,
		Deletable:           false,
		DeprecatedAndHidden: false,
		FeedEnabled:         false,
		Label:               historyLabel,
		LabelPlural:         historyLabel,
		Layoutable:          false,
		Name:                historyObjectName,
		IsSystem:            true,
		Queryable:           true,
		Replicateable:       true,
		Retrieveable:        false,
		Searchable:          false,
		Triggerable:         false,
		Undeletable:         false,
		Updateable:          false,
		Urls: map[string]string{
			"sobject":     fmt.Sprintf("%s/%s", sobjectsBase, historyObjectName),
			"describe":    fmt.Sprintf("%s/%s/describe", sobjectsBase, historyObjectName),
			"rowTemplate": fmt.Sprintf("%s/%s/{ID}", sobjectsBase, historyObjectName),
		},
	}
}

func describeHistoryObjectResponse(version string, historyObjectName string, parent *models.ObjectMetadata, access *contract.EffectiveObjectAccess) describeSObjectResult {
	parentFieldName := historyParentFieldName(historyObjectName)
	historyLabel := parent.Label + " History"
	if historyObjectName == opportunityFieldHistoryObject {
		historyLabel = parent.Label + " Field History"
	}
	readableHistoryFields := make([]string, 0)
	for _, field := range parent.Fields {
		if access.Fields[field.APIName].Readable {
			readableHistoryFields = append(readableHistoryFields, field.APIName)
		}
	}
	fieldPicklist := make([]interface{}, 0, len(readableHistoryFields))
	for _, fieldName := range readableHistoryFields {
		fieldPicklist = append(fieldPicklist, map[string]interface{}{
			"active": true, "defaultValue": false, "label": fieldName, "value": fieldName,
		})
	}
	fields := []sObjectField{
		historyDescribeField("Id", "History ID", "id", nil, true, false, false, nil),
		historyDescribeField(parentFieldName, parent.Label, "reference", []string{parent.APIName}, true, true, false, nil),
		historyDescribeField("Field", "Field", "picklist", nil, true, true, false, fieldPicklist),
		historyDescribeField("OldValue", "Old Value", "anyType", nil, len(readableHistoryFields) > 0, false, false, nil),
		historyDescribeField("NewValue", "New Value", "anyType", nil, len(readableHistoryFields) > 0, false, false, nil),
		historyDescribeField("DataType", "Data Type", "string", nil, true, false, false, nil),
		historyDescribeField("CreatedDate", "Created Date", "datetime", nil, true, false, true, nil),
		historyDescribeField("CreatedById", "Created By", "reference", []string{"User"}, true, false, false, nil),
	}
	sobjectsBase := versionedSObjectsBasePath(version)
	return describeSObjectResult{
		ActionOverrides:       []interface{}{},
		AssociateEntityType:   nil,
		AssociateParentEntity: nil,
		ChildRelationships:    []childRelationship{},
		Createable:            false,
		Custom:                parent.IsCustom,
		Deletable:             false,
		DeprecatedAndHidden:   false,
		FeedEnabled:           false,
		Fields:                fields,
		Label:                 historyLabel,
		LabelPlural:           historyLabel,
		Layoutable:            false,
		Name:                  historyObjectName,
		NamedLayoutInfos:      []interface{}{},
		Queryable:             true,
		RecordTypeInfos:       []recordTypeInfo{},
		Replicateable:         true,
		Retrieveable:          false,
		Searchable:            false,
		SupportedScopes:       []interface{}{},
		SObjectDescribeOption: "FULL",
		Triggerable:           false,
		Undeletable:           false,
		Updateable:            false,
		Urls: map[string]string{
			"sobject":     fmt.Sprintf("%s/%s", sobjectsBase, historyObjectName),
			"describe":    fmt.Sprintf("%s/%s/describe", sobjectsBase, historyObjectName),
			"rowTemplate": fmt.Sprintf("%s/%s/{ID}", sobjectsBase, historyObjectName),
		},
	}
}

func historyDescribeField(name, label, fieldType string, referenceTo []string, readable, filterable, sortable bool, picklistValues []interface{}) sObjectField {
	soapType := "xsd:string"
	switch fieldType {
	case "id", "reference":
		soapType = "tns:ID"
	case "datetime":
		soapType = "xsd:dateTime"
	case "anyType":
		soapType = "xsd:anyType"
	}
	if picklistValues == nil {
		picklistValues = []interface{}{}
	}
	if referenceTo == nil {
		referenceTo = []string{}
	}
	return sObjectField{
		Createable:          false,
		Custom:              false,
		Filterable:          filterable,
		IdLookup:            name == "Id",
		Label:               label,
		Name:                name,
		Nillable:            name != "Id" && !strings.HasSuffix(name, "Id"),
		Permissionable:      false,
		PicklistValues:      picklistValues,
		Readable:            readable,
		ReferenceTo:         referenceTo,
		SearchPrefilterable: true,
		SoapType:            soapType,
		Sortable:            sortable,
		Type:                fieldType,
		Updateable:          false,
	}
}
