package sfcrmappservice

import (
	"context"
	"net/http"
	"net/url"
	"slices"
	"strings"

	"github.com/google/uuid"
	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	pagelayoutsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/pagelayoutresolutionv1"
	defaultsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recorddefaultsv1"
	recordsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordviewv1"
)

type RuntimePreviewCreateDefaultsFunc func(
	context.Context,
	string,
	defaultsemantic.Request,
) (defaultsemantic.Response, error)

type recordDefaultsRoute struct {
	version  string
	kind     string
	identity string
}

func matchRecordDefaultsRoute(path string) (recordDefaultsRoute, bool) {
	parts := strings.Split(strings.Trim(path, "/"), "/")
	if len(parts) != 7 || parts[0] != "services" || parts[1] != "data" ||
		parts[3] != "ui-api" || parts[4] != "record-defaults" ||
		(parts[5] != "create" && parts[5] != "clone") {
		return recordDefaultsRoute{}, false
	}
	if _, supported := recordViewVersions[parts[2]]; !supported {
		return recordDefaultsRoute{}, false
	}
	identity, err := url.PathUnescape(parts[6])
	if err != nil || identity == "" || identity != strings.TrimSpace(identity) ||
		(parts[5] == "create" && !validSearchIdentifier(identity)) {
		return recordDefaultsRoute{}, false
	}
	return recordDefaultsRoute{version: parts[2], kind: parts[5], identity: identity}, true
}

type recordDefaultsResult struct {
	response  map[string]any
	authority recordUIAuthority
}

func recordDefaultFields(authority recordUIAuthority, recordType projectedRecordType) (map[string]any, error) {
	fieldPermissions := fieldPermissionsForDescribe(authority.permissions, authority.spec.APIName)
	fields := make(map[string]any)
	recordTypeFieldName := ""
	for _, field := range authority.spec.Fields {
		permission, found := fieldPermissions[strings.ToLower(field.APIName)]
		if !found || !permission.CanRead || !permission.CanCreate || !field.Createable {
			continue
		}
		value := describeDefaultValue(field, describeFieldType(field))
		if strings.EqualFold(field.APIName, "RecordTypeId") {
			value = recordType.RecordTypeID
			recordTypeFieldName = field.APIName
		}
		fields[field.APIName] = map[string]any{"displayValue": nil, "value": value}
	}
	if recordTypeFieldName != "" {
		projectRecordTypeDisplay(fields, recordTypeFieldName, recordType)
	}
	return fields, nil
}

func (service *Service) readRecordDefaults(
	ctx context.Context,
	bearer string,
	objectID string,
	recordTypeID string,
	parentFieldID *string,
	parentRecordID *string,
) (map[string]any, error) {
	result, err := service.readRecordDefaultsResult(
		ctx, bearer, objectID, recordTypeID, parentFieldID, parentRecordID,
	)
	return result.response, err
}

func (service *Service) readRecordDefaultsResult(
	ctx context.Context,
	bearer string,
	objectID string,
	recordTypeID string,
	parentFieldID *string,
	parentRecordID *string,
) (recordDefaultsResult, error) {
	var authority recordUIAuthority
	var err error
	if recordTypeID != "" {
		authority, err = service.readRecordUIAuthority(ctx, bearer, objectID, recordTypeID)
	} else {
		// The default RecordType is caller authority, not authored layout
		// metadata. Read that fact first, then resolve exactly that RecordType's
		// layout. This avoids treating the wildcard layout as the create layout.
		authority, err = service.readRecordUICatalogAuthority(ctx, objectID)
		if err == nil {
			var selection *permissionsemantic.Selection
			selection, err = recordUIObjectInfoPermissionSelection(authority.spec, authority.specs)
			if err == nil {
				authority, err = service.completeRecordUIAuthority(ctx, bearer, authority, selection)
			}
		}
		if err == nil {
			recordTypeID = authority.recordTypes.defaultRecordTypeID
			if recordTypeID == masterRecordTypeID {
				recordTypeID = ""
			}
			authority.layout, authority.layoutReceipts, err = service.resolvePageLayout(
				ctx, bearer, authority.view, authority.spec.APIName, recordTypeID,
			)
		}
		if err == nil {
			var selection *permissionsemantic.Selection
			selection, err = recordUILayoutAuthoritySelection(authority)
			if err == nil {
				authority, err = service.readRecordUIListFields(ctx, bearer, authority)
			}
			if err == nil {
				firstReceipts := authority.permissions.Receipts
				authority, err = service.completeRecordUIAuthority(ctx, bearer, authority, selection)
				if err == nil && !sameAppMetadataAuthority(firstReceipts, authority.permissions.Receipts) {
					err = unavailablePageLayout()
				}
			}
		}
	}
	if err != nil {
		return recordDefaultsResult{}, err
	}
	if !authority.objectPermission.CanCreate {
		return recordDefaultsResult{}, &commonsemantic.Failure{Category: commonsemantic.FailureForbidden}
	}
	if recordTypeID == "" {
		recordTypeID = authority.recordTypes.defaultRecordTypeID
		if recordTypeID == "" {
			return recordDefaultsResult{}, unavailableSearch()
		}
	}
	recordType, found := authority.recordTypes.entry(recordTypeID)
	if !found || !recordType.Available {
		return recordDefaultsResult{}, &commonsemantic.Failure{Category: commonsemantic.FailureMissing}
	}
	layout, layoutAuthority, err := authority.authorizedLayout()
	if err != nil {
		return recordDefaultsResult{}, err
	}
	projectedLayout, err := projectRecordUILayout(
		layout, authority.spec, layoutAuthority, authority.objectPermission, authority.permissions, "Create",
	)
	if err != nil {
		return recordDefaultsResult{}, err
	}
	fields, err := recordDefaultFields(authority, recordType)
	if err != nil {
		return recordDefaultsResult{}, err
	}
	if service.previewCreateDefaults == nil {
		return recordDefaultsResult{}, unavailableSearch()
	}
	minimum := authority.revision
	previewRequest := defaultsemantic.Request{
		ObjectID: objectID, ParentFieldID: parentFieldID, ParentRecordID: parentRecordID,
		MinCatalogRevision: &minimum,
	}
	if err := defaultsemantic.ValidateRequest(ctx, previewRequest); err != nil {
		return recordDefaultsResult{}, err
	}
	preview, err := service.previewCreateDefaults(ctx, bearer, previewRequest)
	if err != nil {
		return recordDefaultsResult{}, err
	}
	if err := defaultsemantic.ValidateResponse(previewRequest, preview); err != nil {
		return recordDefaultsResult{}, err
	}
	if err := validateSearchMetadataAuthority(
		ctx, service.reconcileAppMetadataV3, authority.view, preview.Receipts,
	); err != nil {
		return recordDefaultsResult{}, err
	}
	if !sameAppMetadataAuthority(authority.permissions.Receipts, preview.Receipts) {
		return recordDefaultsResult{}, unavailableSearch()
	}
	siblingMaxDefaultedFields, err := applyRuntimeCreateDefaults(fields, preview.Fields)
	if err != nil {
		return recordDefaultsResult{}, err
	}
	response := map[string]any{
		"objectApiName": authority.spec.APIName,
		"recordTypeId":  recordTypeID,
		"record": map[string]any{
			"apiName": authority.spec.APIName,
			"fields":  fields,
		},
		"objectInfo": projectRecordUIObjectInfo(
			authority.spec, authority.objectPermission, authority.permissions, authority.recordTypes,
			authority.listFields[strings.ToLower(authority.spec.APIName)],
		),
		"layout": projectedLayout,
	}
	if len(siblingMaxDefaultedFields) > 0 {
		response["siblingMaxDefaultedFields"] = siblingMaxDefaultedFields
	}
	return recordDefaultsResult{response: response, authority: authority}, nil
}

func applyRuntimeCreateDefaults(
	fields map[string]any,
	defaults []defaultsemantic.Field,
) ([]string, error) {
	siblingMaxDefaultedFields := make([]string, 0)
	for _, dynamic := range defaults {
		projected, ok := fields[dynamic.Name].(map[string]any)
		if !ok {
			return nil, unavailableSearch()
		}
		switch dynamic.Provenance {
		case defaultsemantic.ProvenanceSiblingMaxPlusOne:
			projected["value"] = *dynamic.Integer
			projected["displayValue"] = nil
			siblingMaxDefaultedFields = append(siblingMaxDefaultedFields, dynamic.Name)
		case defaultsemantic.ProvenanceCurrentPrincipal:
			projected["value"] = *dynamic.Reference
			if dynamic.DisplayValue == nil {
				projected["displayValue"] = nil
			} else {
				projected["displayValue"] = *dynamic.DisplayValue
			}
		default:
			return nil, unavailableSearch()
		}
	}
	return siblingMaxDefaultedFields, nil
}

func cloneKeepsCreateDefault(fieldName string) bool {
	switch strings.ToLower(fieldName) {
	case "ownerid", "recordtypeid":
		return true
	default:
		return false
	}
}

func cloneExcludesField(fieldName string) bool {
	switch strings.ToLower(fieldName) {
	case "createddate", "createdbyid", "lastmodifieddate", "lastmodifiedbyid",
		"systemmodstamp", "isdeleted":
		return true
	default:
		return false
	}
}

func applyCloneSourceFields(response map[string]any, read recordsemantic.ReadResponse) error {
	record, ok := response["record"].(map[string]any)
	if !ok {
		return unavailableSearch()
	}
	fields, ok := record["fields"].(map[string]any)
	if !ok {
		return unavailableSearch()
	}
	for fieldName, projected := range fields {
		if cloneExcludesField(fieldName) {
			delete(fields, fieldName)
			continue
		}
		if cloneKeepsCreateDefault(fieldName) {
			continue
		}
		field, ok := projected.(map[string]any)
		if !ok {
			return unavailableSearch()
		}
		field["value"] = nil
		field["displayValue"] = nil
	}
	displays := make(map[string]string, len(read.ReferenceDisplays))
	for _, display := range read.ReferenceDisplays {
		if display.DisplayName != "" {
			displays[strings.ToLower(display.SourceFieldID)] = display.DisplayName
		}
	}
	attachmentSources := make(map[string]struct{}, len(read.AttachmentSourceFields))
	for _, binding := range read.AttachmentSourceFields {
		attachmentSources[strings.ToLower(binding.SourceFieldID)] = struct{}{}
	}
	for _, source := range read.Root.Fields {
		if cloneKeepsCreateDefault(source.Name) || cloneExcludesField(source.Name) {
			continue
		}
		// Attachment source fields contain provider/storage identities. They are
		// read projections, not values that can safely be copied into a new
		// record, so clone leaves their create defaults empty.
		if _, bound := attachmentSources[strings.ToLower(source.Name)]; bound {
			continue
		}
		target, allowed := fields[source.Name].(map[string]any)
		if !allowed {
			continue
		}
		value, err := recordViewValue(source.Value)
		if err != nil {
			return err
		}
		target["value"] = value
		if display, found := displays[strings.ToLower(source.Name)]; found {
			target["displayValue"] = display
		} else {
			target["displayValue"] = nil
		}
	}
	return nil
}

func cloneSourceRecordTypeID(read recordsemantic.ReadResponse) (string, error) {
	for _, field := range read.Root.Fields {
		if !strings.EqualFold(field.Name, "RecordTypeId") {
			continue
		}
		value, err := recordViewValue(field.Value)
		if err != nil {
			return "", err
		}
		if value == nil {
			return "", nil
		}
		identifier, ok := value.(string)
		if !ok || identifier != strings.TrimSpace(identifier) {
			return "", unavailableSearch()
		}
		return identifier, nil
	}
	return "", nil
}

func cloneObjectSpecForRecordID(
	specs map[string]searchObjectSpec,
	recordID string,
) (searchObjectSpec, error) {
	recordID = strings.TrimSpace(recordID)
	if len(recordID) < 3 {
		return searchObjectSpec{}, &commonsemantic.Failure{Category: commonsemantic.FailureMissing}
	}
	if _, err := uuid.Parse(recordID); err == nil {
		// UUIDs are opaque identities. Their first bytes must never be treated
		// as a Salesforce KeyPrefix; callers with opaque IDs provide `type`.
		return searchObjectSpec{}, &commonsemantic.Failure{Category: commonsemantic.FailureMissing}
	}
	for _, width := range []int{3, 2} {
		if len(recordID) < width {
			continue
		}
		prefix := recordID[:width]
		var resolved searchObjectSpec
		for _, spec := range specs {
			if spec.KeyPrefix != prefix || !describeAuthorityIsCurrent(spec) {
				continue
			}
			if resolved.APIName != "" && resolved.APIName != spec.APIName {
				return searchObjectSpec{}, unavailableSearch()
			}
			resolved = spec
		}
		if resolved.APIName != "" {
			return resolved, nil
		}
	}
	return searchObjectSpec{}, &commonsemantic.Failure{Category: commonsemantic.FailureMissing}
}

func (service *Service) readCloneCatalogAuthority(
	ctx context.Context,
	recordID string,
	objectID string,
) (recordUIAuthority, error) {
	if objectID != "" {
		return service.readRecordUICatalogAuthority(ctx, objectID)
	}
	view, specs, err := readObjectSpecs(ctx, service.appMetadataV3)
	if err != nil {
		return recordUIAuthority{}, err
	}
	spec, err := cloneObjectSpecForRecordID(specs, recordID)
	if err != nil {
		return recordUIAuthority{}, err
	}
	return recordUIAuthority{
		view: view, specs: specs, spec: spec, revision: view.Receipts.CatalogRevision,
	}, nil
}

func (service *Service) readCloneDefaults(
	ctx context.Context,
	bearer string,
	recordID string,
	objectID string,
	recordTypeID string,
) (map[string]any, error) {
	if service.recordView == nil {
		return nil, unavailableSearch()
	}
	if service.authorizeRuntime != nil {
		capabilities := []string{
			permissionsemantic.CapabilityName,
			pagelayoutsemantic.CapabilityName,
			defaultsemantic.CapabilityName,
			recordsemantic.CapabilityName,
		}
		slices.Sort(capabilities)
		delegated, err := service.authorizeRuntime(ctx, bearer, capabilities)
		if err != nil {
			return nil, err
		}
		ctx = delegated
	}
	authority, err := service.readCloneCatalogAuthority(ctx, recordID, objectID)
	if err != nil {
		return nil, err
	}
	read, err := service.recordView.Read(ctx, bearer, recordsemantic.ReadRequest{
		ObjectID:           authority.spec.APIName,
		Selector:           recordsemantic.Selector{Kind: recordsemantic.SelectorRecordID, RecordID: recordID},
		Projection:         recordsemantic.ProjectionEnrichedView,
		MinCatalogRevision: &authority.revision,
	})
	if err != nil {
		return nil, err
	}
	if err := validateSearchMetadataAuthority(
		ctx, service.reconcileAppMetadataV3, authority.view, read.Receipts,
	); err != nil {
		return nil, err
	}
	if err := validateRecordUIReadAccess(read); err != nil {
		return nil, err
	}
	if recordTypeID == "" {
		recordTypeID, err = cloneSourceRecordTypeID(read)
		if err != nil {
			return nil, err
		}
	}
	defaults, err := service.readRecordDefaultsResult(
		ctx, bearer, authority.spec.APIName, recordTypeID, nil, nil,
	)
	if err != nil {
		return nil, err
	}
	if !sameAppMetadataAuthority(defaults.authority.permissions.Receipts, read.Receipts) {
		return nil, unavailableSearch()
	}
	if err := applyCloneSourceFields(defaults.response, read); err != nil {
		return nil, err
	}
	delete(defaults.response, "siblingMaxDefaultedFields")
	return defaults.response, nil
}

func recordDefaultParentContext(request *http.Request) (*string, *string, error) {
	query := request.URL.Query()
	parentFields, hasParentField := query["parentFieldApiName"]
	parentRecords, hasParentRecord := query["parentRecordId"]
	if (hasParentField && len(parentFields) != 1) || (hasParentRecord && len(parentRecords) != 1) {
		return nil, nil, &defaultsemantic.Failure{
			Category: defaultsemantic.FailureInvalidRequest,
		}
	}
	var parentFieldID, parentRecordID *string
	if hasParentField {
		parentFieldID = &parentFields[0]
	}
	if hasParentRecord {
		parentRecordID = &parentRecords[0]
	}
	return parentFieldID, parentRecordID, nil
}

func (service *Service) serveRecordDefaults(writer http.ResponseWriter, request *http.Request) bool {
	route, matched := matchRecordDefaultsRoute(request.URL.EscapedPath())
	if !matched || service.permissionMetadata == nil || service.appMetadataV3 == nil ||
		service.resolvePageLayoutRuntime == nil || (route.kind == "clone" && service.recordView == nil) {
		return false
	}
	if request.Method != http.MethodGet {
		writer.Header().Set("Allow", http.MethodGet)
		writeCRMError(writer, http.StatusMethodNotAllowed, crmwire.SFCodeInvalidRequest, "Method not allowed")
		return true
	}
	service.requests.Add(1)
	if service.draining.Load() {
		service.drainRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusServiceUnavailable, crmwire.SFCodeUnknownException, "Service is draining")
		return true
	}
	bearer, ok := endUserBearer(request.Header.Values("Authorization"))
	if !ok {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusUnauthorized, crmwire.SFCodeInvalidSessionID, "Session expired or invalid")
		return true
	}
	recordTypeID := request.URL.Query().Get("recordTypeId")
	if !validPageLayoutRecordTypeID(recordTypeID, true) {
		service.localRejected.Add(1)
		service.failures.Add(1)
		writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, "Invalid Record Type ID")
		return true
	}
	service.inFlight.Add(1)
	defer service.inFlight.Add(-1)
	ctx, cancel := context.WithTimeout(request.Context(), service.requestTimeout)
	defer cancel()
	var response map[string]any
	var err error
	if route.kind == "clone" {
		objectID := strings.TrimSpace(request.URL.Query().Get("type"))
		if objectID != "" && !validSearchIdentifier(objectID) {
			service.localRejected.Add(1)
			service.failures.Add(1)
			writeCRMError(writer, http.StatusBadRequest, crmwire.SFCodeInvalidRequest, "Invalid object type")
			return true
		}
		response, err = service.readCloneDefaults(ctx, bearer, route.identity, objectID, recordTypeID)
	} else {
		var parentFieldID, parentRecordID *string
		parentFieldID, parentRecordID, err = recordDefaultParentContext(request)
		if err == nil {
			response, err = service.readRecordDefaults(
				ctx, bearer, route.identity, recordTypeID, parentFieldID, parentRecordID,
			)
		}
	}
	if err != nil {
		service.failures.Add(1)
		writeSearchFailure(writer, err)
		return true
	}
	service.successes.Add(1)
	writeJSON(writer, http.StatusOK, response)
	return true
}
