package mapping

import (
	"context"
	"encoding/json"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationnavigationv1"
	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/protobuf/proto"
)

func navigationShape() error { return requirePlatformSemanticShape(semantic.CapabilityName) }
func navigationItemsToWire(items []semantic.Item) *runtimev1.ApplicationNavigationItems {
	result := &runtimev1.ApplicationNavigationItems{Values: make([]*runtimev1.ApplicationNavigationItem, 0, len(items))}
	for _, v := range items {
		item := &runtimev1.ApplicationNavigationItem{Id: v.ID, Label: v.Label, Icon: v.Icon}
		switch {
		case v.Target.Resource != nil:
			r := v.Target.Resource
			item.Target = &runtimev1.ApplicationNavigationItem_Resource{Resource: &runtimev1.ApplicationNavigationResourceTarget{ResourceType: r.ResourceType, ResourceId: r.ResourceID}}
		case v.Target.Page != nil:
			p := v.Target.Page
			item.Target = &runtimev1.ApplicationNavigationItem_Page{Page: &runtimev1.ApplicationNavigationPageTarget{Id: p.ID}}
		case v.Target.URL != nil:
			item.Target = &runtimev1.ApplicationNavigationItem_Url{Url: *v.Target.URL}
		}
		result.Values = append(result.Values, item)
	}
	return result
}
func navigationItemsFromWire(items *runtimev1.ApplicationNavigationItems) ([]semantic.Item, error) {
	if items == nil || len(items.Values) > semantic.MaxItems {
		return nil, invalidNavigationRequest()
	}
	result := make([]semantic.Item, 0, len(items.Values))
	for _, v := range items.Values {
		if v == nil {
			return nil, invalidNavigationRequest()
		}
		item := semantic.Item{ID: v.Id, Label: v.Label, Icon: v.Icon}
		switch target := v.Target.(type) {
		case *runtimev1.ApplicationNavigationItem_Resource:
			if target.Resource == nil {
				return nil, invalidNavigationRequest()
			}
			item.Target.Resource = &semantic.ResourceTarget{ResourceType: target.Resource.ResourceType, ResourceID: target.Resource.ResourceId}
		case *runtimev1.ApplicationNavigationItem_Page:
			if target.Page == nil {
				return nil, invalidNavigationRequest()
			}
			item.Target.Page = &semantic.PageTarget{ID: target.Page.Id}
		case *runtimev1.ApplicationNavigationItem_Url:
			u := target.Url
			item.Target.URL = &u
		default:
			return nil, invalidNavigationRequest()
		}
		result = append(result, item)
	}
	if err := semantic.ValidateItems(result); err != nil {
		return nil, err
	}
	return result, nil
}
func navigationDocumentToWire(d semantic.Document) *runtimev1.ApplicationNavigationDocument {
	return &runtimev1.ApplicationNavigationDocument{ApplicationId: d.ApplicationID, Revision: d.Revision, Items: navigationItemsToWire(d.Items)}
}
func navigationDocumentFromWire(d *runtimev1.ApplicationNavigationDocument) (semantic.Document, error) {
	if d == nil {
		return semantic.Document{}, internalPlatformWire()
	}
	items, err := navigationItemsFromWire(d.Items)
	if err != nil {
		return semantic.Document{}, internalPlatformWire()
	}
	return semantic.Document{ApplicationID: d.ApplicationId, Revision: d.Revision, Items: items}, nil
}

func ToWireNavigationReadRequest(ctx context.Context, r semantic.ReadRequest) (*runtimev1.ApplicationNavigationServiceReadRequest, error) {
	if err := navigationShape(); err != nil {
		return nil, err
	}
	if err := semantic.ValidateReadRequest(ctx, r); err != nil {
		return nil, err
	}
	return &runtimev1.ApplicationNavigationServiceReadRequest{ApplicationId: r.ApplicationID}, nil
}
func FromWireNavigationReadRequest(ctx context.Context, r *runtimev1.ApplicationNavigationServiceReadRequest) (semantic.ReadRequest, error) {
	if err := navigationShape(); err != nil {
		return semantic.ReadRequest{}, err
	}
	if r == nil {
		return semantic.ReadRequest{}, invalidNavigationRequest()
	}
	v := semantic.ReadRequest{ApplicationID: r.ApplicationId}
	return v, semantic.ValidateReadRequest(ctx, v)
}
func ToWireNavigationReadResponse(r semantic.ReadRequest, v semantic.ReadResponse) (*runtimev1.ApplicationNavigationServiceReadResponse, error) {
	if err := navigationShape(); err != nil {
		return nil, err
	}
	if err := semantic.ValidateReadResponse(r, v); err != nil {
		return nil, err
	}
	return &runtimev1.ApplicationNavigationServiceReadResponse{Document: navigationDocumentToWire(v.Document), Receipts: toWireReceipts(v.Receipts, platformCapability(semantic.CapabilityName))}, nil
}
func FromWireNavigationReadResponse(r semantic.ReadRequest, v *runtimev1.ApplicationNavigationServiceReadResponse) (semantic.ReadResponse, error) {
	if err := navigationShape(); err != nil {
		return semantic.ReadResponse{}, err
	}
	if v == nil {
		return semantic.ReadResponse{}, internalPlatformWire()
	}
	receipts, err := fromWireReceipts(v.Receipts, platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.ReadResponse{}, err
	}
	if err := validateNavigationWireResponseBudget(v, []*runtimev1.ApplicationNavigationDocument{v.Document}, receipts); err != nil {
		return semantic.ReadResponse{}, err
	}
	d, err := navigationDocumentFromWire(v.Document)
	if err != nil {
		return semantic.ReadResponse{}, err
	}
	result := semantic.ReadResponse{Document: d, Receipts: receipts}
	return result, semantic.ValidateReadResponse(r, result)
}
func ToWireNavigationResolveRequest(ctx context.Context, r semantic.ResolveRequest) (*runtimev1.ApplicationNavigationServiceResolveRequest, error) {
	if err := navigationShape(); err != nil {
		return nil, err
	}
	if err := semantic.ValidateResolveRequest(ctx, r); err != nil {
		return nil, err
	}
	return &runtimev1.ApplicationNavigationServiceResolveRequest{ApplicationIds: append([]string{}, r.ApplicationIDs...)}, nil
}
func FromWireNavigationResolveRequest(ctx context.Context, r *runtimev1.ApplicationNavigationServiceResolveRequest) (semantic.ResolveRequest, error) {
	if err := navigationShape(); err != nil {
		return semantic.ResolveRequest{}, err
	}
	if r == nil || len(r.ApplicationIds) > semantic.MaxApplications {
		return semantic.ResolveRequest{}, invalidNavigationRequest()
	}
	v := semantic.ResolveRequest{ApplicationIDs: append([]string{}, r.ApplicationIds...)}
	return v, semantic.ValidateResolveRequest(ctx, v)
}
func ToWireNavigationResolveResponse(r semantic.ResolveRequest, v semantic.ResolveResponse) (*runtimev1.ApplicationNavigationServiceResolveResponse, error) {
	if err := navigationShape(); err != nil {
		return nil, err
	}
	if err := semantic.ValidateResolveResponse(r, v); err != nil {
		return nil, err
	}
	result := &runtimev1.ApplicationNavigationServiceResolveResponse{Documents: make([]*runtimev1.ApplicationNavigationDocument, 0, len(v.Documents)), Receipts: toWireReceipts(v.Receipts, platformCapability(semantic.CapabilityName))}
	for _, d := range v.Documents {
		result.Documents = append(result.Documents, navigationDocumentToWire(d))
	}
	return result, nil
}
func FromWireNavigationResolveResponse(r semantic.ResolveRequest, v *runtimev1.ApplicationNavigationServiceResolveResponse) (semantic.ResolveResponse, error) {
	if err := navigationShape(); err != nil {
		return semantic.ResolveResponse{}, err
	}
	if v == nil {
		return semantic.ResolveResponse{}, internalPlatformWire()
	}
	receipts, err := fromWireReceipts(v.Receipts, platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.ResolveResponse{}, err
	}
	if err := validateNavigationWireResponseBudget(v, v.Documents, receipts); err != nil {
		return semantic.ResolveResponse{}, err
	}
	result := semantic.ResolveResponse{Documents: make([]semantic.Document, 0, len(v.Documents)), Receipts: receipts}
	for _, raw := range v.Documents {
		d, err := navigationDocumentFromWire(raw)
		if err != nil {
			return semantic.ResolveResponse{}, err
		}
		result.Documents = append(result.Documents, d)
	}
	return result, semantic.ValidateResolveResponse(r, result)
}
func ToWireNavigationSaveRequest(ctx context.Context, r semantic.SaveRequest) (*runtimev1.ApplicationNavigationServiceSaveRequest, error) {
	if err := navigationShape(); err != nil {
		return nil, err
	}
	if err := semantic.ValidateSaveRequest(ctx, r); err != nil {
		return nil, err
	}
	return &runtimev1.ApplicationNavigationServiceSaveRequest{ApplicationId: r.ApplicationID, ExpectedRevision: r.ExpectedRevision, IdempotencyKey: r.IdempotencyKey, Items: navigationItemsToWire(r.Items)}, nil
}
func FromWireNavigationSaveRequest(ctx context.Context, r *runtimev1.ApplicationNavigationServiceSaveRequest) (semantic.SaveRequest, error) {
	if err := navigationShape(); err != nil {
		return semantic.SaveRequest{}, err
	}
	if r == nil {
		return semantic.SaveRequest{}, invalidNavigationRequest()
	}
	items, err := navigationItemsFromWire(r.Items)
	if err != nil {
		return semantic.SaveRequest{}, err
	}
	v := semantic.SaveRequest{ApplicationID: r.ApplicationId, ExpectedRevision: r.ExpectedRevision, IdempotencyKey: r.IdempotencyKey, Items: items}
	return v, semantic.ValidateSaveRequest(ctx, v)
}
func ToWireNavigationSaveResponse(r semantic.SaveRequest, v semantic.SaveResponse) (*runtimev1.ApplicationNavigationServiceSaveResponse, error) {
	if err := navigationShape(); err != nil {
		return nil, err
	}
	if err := semantic.ValidateSaveResponse(r, v); err != nil {
		return nil, err
	}
	return &runtimev1.ApplicationNavigationServiceSaveResponse{Document: navigationDocumentToWire(v.Document), Changed: v.Changed, Receipts: toWireReceipts(v.Receipts, platformCapability(semantic.CapabilityName))}, nil
}
func FromWireNavigationSaveResponse(r semantic.SaveRequest, v *runtimev1.ApplicationNavigationServiceSaveResponse) (semantic.SaveResponse, error) {
	if err := navigationShape(); err != nil {
		return semantic.SaveResponse{}, err
	}
	if v == nil {
		return semantic.SaveResponse{}, internalPlatformWire()
	}
	receipts, err := fromWireReceipts(v.Receipts, platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.SaveResponse{}, err
	}
	if err := validateNavigationWireResponseBudget(v, []*runtimev1.ApplicationNavigationDocument{v.Document}, receipts); err != nil {
		return semantic.SaveResponse{}, err
	}
	d, err := navigationDocumentFromWire(v.Document)
	if err != nil {
		return semantic.SaveResponse{}, err
	}
	result := semantic.SaveResponse{Document: d, Changed: v.Changed, Receipts: receipts}
	return result, semantic.ValidateSaveResponse(r, result)
}

// Derive minimum per-item JSON overhead from the sole semantic schema. Empty
// strings contribute no content bytes; optional fields, escaping, separators,
// document framing, and response framing can only increase this lower bound.
// This follows the existing layout-authoring semantic-floor budget pattern.
var navigationItemFloors = func() struct{ resource, page, url int } {
	size := func(target semantic.Target) int {
		encoded, err := json.Marshal(semantic.Item{Target: target})
		if err != nil {
			panic(err)
		} // Fixed string-only semantic types must encode.
		return len(encoded)
	}
	emptyURL := ""
	return struct{ resource, page, url int }{
		size(semantic.Target{Resource: &semantic.ResourceTarget{}}),
		size(semantic.Target{Page: &semantic.PageTarget{}}),
		size(semantic.Target{URL: &emptyURL}),
	}
}()

// Bound the complete wire payload before allocating semantic items. Each loop
// is count-bounded; proto.Size reads string lengths without serializing them.
// A semantic lower bound then rejects aggregate expansion without materializing
// items or parsing URLs. Final semantic validation owns exact JSON accounting.
func validateNavigationWireResponseBudget(message proto.Message, documents []*runtimev1.ApplicationNavigationDocument, receipts common.Receipts) error {
	if len(documents) > semantic.MaxApplications {
		return internalPlatformWire()
	}
	for _, document := range documents {
		if document == nil || document.Items == nil || len(document.Items.Values) > semantic.MaxItems {
			return internalPlatformWire()
		}
	}
	if proto.Size(message) > envelope.MaxResponseWireBytes {
		return &common.Failure{Category: common.FailureResourceExhausted, LimitKind: common.LimitWireFrame, Maximum: envelope.MaxResponseWireBytes}
	}
	budget := newWireStringBudget(semantic.MaxSemanticResponseBytes)
	exhausted := func() error {
		return platformLimitFailure(common.LimitSemanticResponse, semantic.MaxSemanticResponseBytes)
	}
	if !budget.take(receipts.ProtocolVersion, receipts.CapabilityVersion) {
		return exhausted()
	}
	for _, document := range documents {
		if !budget.take(document.ApplicationId) {
			return exhausted()
		}
		for _, item := range document.Items.Values {
			if item == nil {
				return internalPlatformWire()
			}
			floor := 0
			switch target := item.Target.(type) {
			case *runtimev1.ApplicationNavigationItem_Resource:
				if target == nil || target.Resource == nil {
					return internalPlatformWire()
				}
				floor = navigationItemFloors.resource
				if !budget.take(target.Resource.ResourceType, target.Resource.ResourceId) {
					return exhausted()
				}
			case *runtimev1.ApplicationNavigationItem_Page:
				if target == nil || target.Page == nil {
					return internalPlatformWire()
				}
				floor = navigationItemFloors.page
				if !budget.take(target.Page.Id) {
					return exhausted()
				}
			case *runtimev1.ApplicationNavigationItem_Url:
				if target == nil {
					return internalPlatformWire()
				}
				floor = navigationItemFloors.url
				if !budget.take(target.Url) {
					return exhausted()
				}
			default:
				return internalPlatformWire()
			}
			if floor > budget.remaining {
				return exhausted()
			}
			budget.remaining -= floor
			if !budget.take(item.Id, item.Label, item.Icon) {
				return exhausted()
			}
		}
	}

	return nil
}

func invalidNavigationRequest() error {
	return &common.Failure{Category: common.FailureInvalidRequest, InvalidComponent: common.InvalidComponentOperation}
}
