package packs

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

	"github.com/nexuscrm/files"
)

type sourceBackedHrefRewriteFieldsPayload struct {
	Fields []files.HrefRewriteField `json:"fields"`
}

type runtimeStartupShallowHrefRewriteFieldProjection struct {
	ParentApiName string `json:"parentApiName"`
	FieldApiName  string `json:"fieldApiName"`
}

func newSourceBackedHrefRewriteFieldsComponent(packName string, component ManifestComponent, payload []byte) (PackComponent, []files.HrefRewriteField, error) {
	expected, err := hrefRewriteFieldsProjection(payload, componentPayloadLabel(component))
	if err != nil {
		return nil, nil, err
	}
	return newHrefRewriteFieldsManagedComponentFromFields(packName, component, payload, expected), expected, nil
}

// newHrefRewriteFieldsManagedComponentFromFields builds the managed component
// from an already-parsed/validated files.HrefRewriteField slice plus the RAW
// payload bytes and the engine ManifestComponent. It is shared by the internal
// build path (newSourceBackedHrefRewriteFieldsComponent) and the engine
// implementation of cp.PresentationComponentBuildContext.AddHrefRewriteFieldsComponent.
// The payload field is the raw bytes (never re-marshalled) so ExpectedPayload is
// identical across paths, and expectedProjection returns the parsed field slice
// so a deep-compare over ExpectedProjection() bites on parsed-content drift.
func newHrefRewriteFieldsManagedComponentFromFields(packName string, component ManifestComponent, payload []byte, fields []files.HrefRewriteField) PackComponent {
	expected := append([]files.HrefRewriteField(nil), fields...)
	return &sourceBackedManagedComponent{
		packName:          packName,
		manifestComponent: component,
		payload:           payload,
		expectedProjection: func() (any, error) {
			return expected, nil
		},
		liveProjection: func(_ context.Context, _ VerifyDeps) (any, error) {
			return expected, nil
		},
		apply: func(_ context.Context, _ ApplyDeps) error {
			return nil
		},
	}
}

func hrefRewriteFieldsProjection(payload []byte, source string) ([]files.HrefRewriteField, error) {
	var parsed sourceBackedHrefRewriteFieldsPayload
	if err := json.Unmarshal(payload, &parsed); err != nil {
		return nil, fmt.Errorf("parse %s: %w", source, err)
	}
	return validateHrefRewriteFields(parsed.Fields, source)
}

func validateHrefRewriteFields(fields []files.HrefRewriteField, source string) ([]files.HrefRewriteField, error) {
	if len(fields) == 0 {
		return nil, fmt.Errorf("%s declares no href rewrite fields", source)
	}
	seen := map[string]files.HrefRewriteField{}
	out := make([]files.HrefRewriteField, 0, len(fields))
	for i, field := range fields {
		field.ParentApiName = strings.TrimSpace(field.ParentApiName)
		field.FieldApiName = strings.TrimSpace(field.FieldApiName)
		if field.ParentApiName == "" || field.FieldApiName == "" {
			return nil, fmt.Errorf("%s fields[%d] has empty parentApiName or fieldApiName", source, i)
		}
		key := strings.ToLower(field.ParentApiName) + "\x00" + strings.ToLower(field.FieldApiName)
		if previous, ok := seen[key]; ok {
			return nil, fmt.Errorf("%s declares duplicate href rewrite field %s/%s and %s/%s", source, previous.ParentApiName, previous.FieldApiName, field.ParentApiName, field.FieldApiName)
		}
		seen[key] = field
		out = append(out, field)
	}
	return out, nil
}

func validateHrefRewriteFieldsForObjectSpecs(fields []files.HrefRewriteField, specs []ObjectSpec) error {
	if len(fields) == 0 {
		return nil
	}
	objectFields := map[string]map[string]string{}
	for _, spec := range specs {
		name := strings.TrimSpace(spec.APIName)
		if name == "" {
			continue
		}
		key := strings.ToLower(name)
		if objectFields[key] == nil {
			objectFields[key] = map[string]string{}
		}
		for _, specField := range spec.Fields {
			fieldName := strings.TrimSpace(specField.APIName)
			if fieldName == "" {
				continue
			}
			objectFields[key][strings.ToLower(fieldName)] = fieldName
		}
	}
	for _, field := range fields {
		parent := strings.TrimSpace(field.ParentApiName)
		fieldName := strings.TrimSpace(field.FieldApiName)
		fieldsByName, ok := objectFields[strings.ToLower(parent)]
		if !ok {
			return fmt.Errorf("href_rewrite_fields parent object %s is not declared by object_spec", parent)
		}
		if _, ok := fieldsByName[strings.ToLower(fieldName)]; !ok {
			return fmt.Errorf("href_rewrite_fields field %s/%s is not declared by object_spec", parent, fieldName)
		}
	}
	return nil
}

func hrefRewriteFieldStartupProjection(field files.HrefRewriteField) (string, error) {
	return marshalRuntimeStartupShallowProjection(runtimeStartupShallowHrefRewriteFieldProjection{
		ParentApiName: strings.TrimSpace(field.ParentApiName),
		FieldApiName:  strings.TrimSpace(field.FieldApiName),
	})
}

func runtimeStartupShallowHrefRewriteFieldLookupProjection(row runtimeStartupShallowDeclaredRow, declaredProjection string) (runtimeStartupShallowHrefRewriteFieldProjection, error) {
	var p runtimeStartupShallowHrefRewriteFieldProjection
	if err := json.Unmarshal([]byte(declaredProjection), &p); err != nil {
		return runtimeStartupShallowHrefRewriteFieldProjection{}, err
	}
	p.ParentApiName = strings.TrimSpace(p.ParentApiName)
	p.FieldApiName = strings.TrimSpace(p.FieldApiName)
	if JoinComponentBusinessKey(p.ParentApiName, p.FieldApiName) != row.BusinessKey {
		return runtimeStartupShallowHrefRewriteFieldProjection{}, fmt.Errorf("parentApiName/fieldApiName %q/%q does not match business key %q", p.ParentApiName, p.FieldApiName, row.BusinessKey)
	}
	return p, nil
}

func hrefRewriteFieldsFromStartupShallowRows(rows []runtimeStartupShallowDeclaredRow) ([]files.HrefRewriteField, error) {
	fields := make([]files.HrefRewriteField, 0)
	for _, row := range rows {
		if row.Kind != ComponentKindHrefRewriteFields {
			continue
		}
		projection, _, err := normalizeRuntimeStartupShallowDeclaredProjection(row)
		if err != nil {
			return nil, fmt.Errorf("href_rewrite_fields %s projection: %w", row.BusinessKey, err)
		}
		p, err := runtimeStartupShallowHrefRewriteFieldLookupProjection(row, projection)
		if err != nil {
			return nil, fmt.Errorf("href_rewrite_fields %s projection: %w", row.BusinessKey, err)
		}
		fields = append(fields, files.HrefRewriteField{
			ParentApiName: p.ParentApiName,
			FieldApiName:  p.FieldApiName,
		})
	}
	return files.NormalizeHrefRewriteFields(fields), nil
}
