package runtimeapi

import (
	"context"
	"errors"
	"log"
	"net/http"
	"strings"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/telemetry"
	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/records"
	"github.com/nexuscrm/shared/pkg/models"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	attachmentsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordattachmentsv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordviewv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
)

// RecordViewEngine is the current L0 CRUD/RLS/FLS continuation for the
// coarse record-view read. It intentionally has no attachment resolve method:
// attachment source resolution is a separately admitted proof-bearing RPC.
type RecordViewEngine interface {
	ReadRecordView(context.Context, semantic.ReadRequest, *models.UserSession) (*records.RuntimeRecordViewResult, error)
}

type AttachmentSourceEngine interface {
	ResolveAttachmentSources(context.Context, attachmentsemantic.ResolveRequest, *models.UserSession) ([]attachmentsemantic.SourceResult, error)
}

// RecordViewServer implements only ReadRecordView. Embedding the generated
// unimplemented server leaves the historical ResolveRecordAttachmentSources
// method fail-closed rather than accidentally reintroducing its unproven
// source-value-only semantics.
type RecordViewServer struct {
	runtimev1.UnimplementedRecordViewReadServiceServer
	engine      RecordViewEngine
	receipts    ReceiptSource
	metrics     *recordsMetrics
	permissions *EffectivePermissionServer
}

func NewRecordViewServer(engine RecordViewEngine, receipts ReceiptSource, metrics *recordsMetrics) (*RecordViewServer, error) {
	if engine == nil {
		return nil, errors.New("runtime-api: record-view engine is required")
	}
	if metrics == nil {
		metrics = newRecordsMetrics(nil)
	}
	return &RecordViewServer{engine: engine, receipts: receipts, metrics: metrics}, nil
}

func (s *RecordViewServer) ReadRecordView(ctx context.Context, wireRequest *runtimev1.ReadRecordViewRequest) (*runtimev1.ReadRecordViewResponse, error) {
	trusted, ok := trustedCallFromContext(ctx)
	if !ok {
		return nil, mapping.ToRecordViewStatusError(&semantic.Failure{Category: semantic.FailureUnauthenticated})
	}
	request, err := mapping.FromWireReadRecordViewRequest(ctx, wireRequest)
	if err != nil {
		return nil, mapping.ToRecordViewStatusError(err)
	}
	receipts, err := recordViewExecutionReceipts(ctx, s.receipts)
	if err != nil {
		return nil, mapping.ToRecordViewStatusError(err)
	}
	receipts = receiptsForCapability(receipts, semantic.CapabilityName)
	if request.MinCatalogRevision != nil && *request.MinCatalogRevision > receipts.CatalogRevision {
		return nil, mapping.ToRecordViewStatusError(&semantic.Failure{
			Category: semantic.FailureStaleState, Receipts: &receipts, StaleAxis: semantic.StaleAxisCatalog,
			RequiredRevision: *request.MinCatalogRevision, ServedRevision: receipts.CatalogRevision,
		})
	}
	view, ok := RuntimeMetadataViewFromContext(ctx)
	if !ok || view == nil {
		return nil, mapping.ToRecordViewStatusError(&semantic.Failure{Category: semantic.FailureUnavailable})
	}
	user := trusted.UserSession()
	repositoryCtx, sqlCounter := telemetry.WithSQLStatementCounter(ctx)
	var selectedAuthority *semantic.SelectedAuthority
	if request.Authority != nil {
		permissionView, ok := effectivePermissionMetadataViewFromContext(ctx)
		if !ok || s.permissions == nil {
			return nil, mapping.ToRecordViewStatusError(&semantic.Failure{Category: semantic.FailureUnavailable})
		}
		var effectiveAuthority *permissionsemantic.Response
		selectedAuthority, effectiveAuthority, err = s.permissions.readSelectedRecordAuthority(
			repositoryCtx, permissionView, receipts, trusted, request.ObjectID, *request.Authority,
		)
		if err != nil {
			return nil, mapping.ToRecordViewStatusError(err)
		}
		repositoryCtx, err = bindEffectiveRecordReadAuthority(
			repositoryCtx, user, request.ObjectID, *request.Authority, selectedAuthority, effectiveAuthority,
		)
		if err != nil {
			return nil, mapping.ToRecordViewStatusError(err)
		}
	}
	result, err := s.engine.ReadRecordView(repositoryCtx, request, &user)
	s.metrics.observeRepository(recordsOperationRecordViewRead, sqlCounter.Count())
	if err != nil {
		log.Printf("runtime record view: engine read failed: %v", err)
		return nil, mapping.ToRecordViewStatusError(classifyRecordViewEngineError(err))
	}
	if result == nil {
		return nil, mapping.ToRecordViewStatusError(&semantic.Failure{Category: semantic.FailureInternal})
	}
	root, err := semanticRecordViewRoot(result.Root, view, request.ObjectID)
	if err != nil {
		log.Printf("runtime record view: neutral root projection failed: %v", err)
		return nil, mapping.ToRecordViewStatusError(err)
	}
	response := semantic.ReadResponse{
		RecordID: result.RecordID, Root: root, Access: result.Access, Receipts: receipts,
		ReferenceDisplays:      append([]semantic.ReferenceDisplay(nil), result.ReferenceDisplays...),
		AttachmentSourceFields: append([]semantic.AttachmentSourceField(nil), result.AttachmentSourceFields...),
	}
	response.Authority = selectedAuthority
	wireResponse, err := mapping.ToWireReadRecordViewResponse(request, response)
	if err != nil {
		log.Printf("runtime record view: wire response validation failed: %v", err)
		return nil, mapping.ToRecordViewStatusError(err)
	}
	return wireResponse, nil
}

func bindEffectiveRecordReadAuthority(
	ctx context.Context,
	user models.UserSession,
	objectID string,
	selection semantic.AuthoritySelection,
	authority *semantic.SelectedAuthority,
	effective *permissionsemantic.Response,
) (context.Context, error) {
	if authority == nil || effective == nil {
		return ctx, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	sections := make(map[semantic.AuthoritySection]struct{}, len(selection.Sections))
	for _, section := range selection.Sections {
		sections[section] = struct{}{}
	}
	for _, required := range []semantic.AuthoritySection{
		semantic.AuthoritySystemPermissions,
		semantic.AuthorityCustomPermissions,
		semantic.AuthorityObjectPermissions,
		semantic.AuthorityFieldPermissions,
	} {
		if _, ok := sections[required]; !ok {
			// A narrower response is still valid for its L1 consumer, but it is
			// insufficient to replace Records' complete CRUD/FLS authority input.
			return ctx, nil
		}
	}
	if len(authority.ObjectPermissions) != 1 ||
		!strings.EqualFold(authority.ObjectPermissions[0].ResourceType, objectID) {
		return ctx, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	systemPermissions := make(map[string]bool, len(effective.SystemPermissions))
	for _, permission := range effective.SystemPermissions {
		systemPermissions[permission.PermissionID] = permission.Granted
	}
	customPermissions := make(map[string]bool, len(effective.CustomPermissions))
	for _, permission := range effective.CustomPermissions {
		customPermissions[permission.PermissionID] = permission.Granted
	}
	fieldsByObject := make(map[string][]models.FieldPermissions)
	for _, field := range effective.FieldPermissions {
		key := strings.ToLower(strings.TrimSpace(field.ResourceType))
		fieldsByObject[key] = append(fieldsByObject[key], models.FieldPermissions{
			SobjectType: field.ResourceType, Field: field.FieldName,
			PermissionsRead: field.CanRead, PermissionsEdit: field.CanEdit,
		})
	}
	bound := ctx
	for _, object := range effective.ObjectPermissions {
		objectPermission := models.ObjectPermissions{
			SobjectType:     object.ResourceType,
			PermissionsRead: object.CanRead, PermissionsCreate: object.CanCreate,
			PermissionsEdit: object.CanEdit, PermissionsDelete: object.CanDelete,
			PermissionsViewAllRecords:   object.CanViewAll,
			PermissionsModifyAllRecords: object.CanModifyAll,
		}
		var err error
		bound, err = permissions.WithEffectiveRecordReadProjection(
			bound, user.ID, user.ProfileID, object.ResourceType,
			systemPermissions, customPermissions, objectPermission,
			fieldsByObject[strings.ToLower(strings.TrimSpace(object.ResourceType))],
		)
		if err != nil {
			return ctx, &semantic.Failure{Category: semantic.FailureUnavailable}
		}
	}
	return bound, nil
}

// AttachmentSourceServer owns the proof-bearing source-to-file continuation.
// It is a separate generated service so an old source-value-only client cannot
// call the implementation by accident.
type AttachmentSourceServer struct {
	runtimev1.UnimplementedRecordAttachmentSourceResolutionServiceServer
	engine   AttachmentSourceEngine
	receipts ReceiptSource
	metrics  *recordsMetrics
}

func NewAttachmentSourceServer(engine AttachmentSourceEngine, receipts ReceiptSource, metrics *recordsMetrics) (*AttachmentSourceServer, error) {
	if engine == nil {
		return nil, errors.New("runtime-api: attachment source engine is required")
	}
	if metrics == nil {
		metrics = newRecordsMetrics(nil)
	}
	return &AttachmentSourceServer{engine: engine, receipts: receipts, metrics: metrics}, nil
}

func (s *AttachmentSourceServer) ResolveAttachmentSources(ctx context.Context, wireRequest *runtimev1.ResolveAttachmentSourcesRequest) (*runtimev1.ResolveAttachmentSourcesResponse, error) {
	trusted, ok := trustedCallFromContext(ctx)
	if !ok {
		return nil, mapping.ToRecordAttachmentSourceStatusError(&attachmentsemantic.Failure{Category: attachmentsemantic.FailureUnauthenticated})
	}
	request, err := mapping.FromWireResolveAttachmentSourcesRequest(ctx, wireRequest)
	if err != nil {
		return nil, mapping.ToRecordAttachmentSourceStatusError(err)
	}
	receipts, err := attachmentSourceExecutionReceipts(ctx, s.receipts)
	if err != nil {
		return nil, mapping.ToRecordAttachmentSourceStatusError(err)
	}
	receipts = receiptsForCapability(receipts, attachmentsemantic.CapabilityName)
	if request.MinCatalogRevision != nil && *request.MinCatalogRevision > receipts.CatalogRevision {
		return nil, mapping.ToRecordAttachmentSourceStatusError(&attachmentsemantic.Failure{
			Category: attachmentsemantic.FailureStaleState, Receipts: &receipts, StaleAxis: attachmentsemantic.StaleAxisCatalog,
			RequiredRevision: *request.MinCatalogRevision, ServedRevision: receipts.CatalogRevision,
		})
	}
	if view, ok := RuntimeMetadataViewFromContext(ctx); !ok || view == nil {
		return nil, mapping.ToRecordAttachmentSourceStatusError(&attachmentsemantic.Failure{Category: attachmentsemantic.FailureUnavailable})
	}
	user := trusted.UserSession()
	repositoryCtx, sqlCounter := telemetry.WithSQLStatementCounter(ctx)
	results, err := s.engine.ResolveAttachmentSources(repositoryCtx, request, &user)
	s.metrics.observeRepository(recordsOperationAttachmentSourceResolve, sqlCounter.Count())
	if err != nil {
		return nil, mapping.ToRecordAttachmentSourceStatusError(classifyAttachmentSourceEngineError(err))
	}
	response := attachmentsemantic.ResolveResponse{Results: results, Receipts: receipts}
	wireResponse, err := mapping.ToWireResolveAttachmentSourcesResponse(request, response)
	if err != nil {
		return nil, mapping.ToRecordAttachmentSourceStatusError(err)
	}
	return wireResponse, nil
}

func recordViewExecutionReceipts(ctx context.Context, source ReceiptSource) (semantic.Receipts, error) {
	if scope, ok := runtimeExecutionScopeFromContext(ctx); ok {
		return scope.receipts, nil
	}
	if source == nil {
		return semantic.Receipts{}, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	return source.Receipts(ctx)
}

func attachmentSourceExecutionReceipts(ctx context.Context, source ReceiptSource) (attachmentsemantic.Receipts, error) {
	if scope, ok := runtimeExecutionScopeFromContext(ctx); ok {
		return scope.receipts, nil
	}
	if source == nil {
		return attachmentsemantic.Receipts{}, &attachmentsemantic.Failure{Category: attachmentsemantic.FailureUnavailable}
	}
	return source.Receipts(ctx)
}

// semanticRecordViewRoot keeps the Runtime response closed over the admitted
// immutable view. The CRUD engine may have loaded physical fields outside that
// view for its own authorization work; none may cross this boundary.
func semanticRecordViewRoot(row models.SObject, view *catalogsnapshot.RuntimeMetadataView, objectID string) (semantic.Record, error) {
	if view == nil {
		return semantic.Record{}, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	schema, ok := view.Object(objectID)
	if !ok || schema == nil {
		return semantic.Record{}, &semantic.Failure{Category: semantic.FailureInternal}
	}
	filtered := make(models.SObject, len(schema.Fields)+1)
	for _, field := range schema.Fields {
		if value, present := row[field.APIName]; present {
			filtered[field.APIName] = value
		}
	}
	for key, value := range row {
		if models.IsRecordMetadataKey(key) {
			filtered[key] = value
		}
	}
	return semanticQueryRecord(filtered, view, objectID, 0)
}

func classifyRecordViewEngineError(err error) error {
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return err
	}
	var failure *semantic.Failure
	if errors.As(err, &failure) {
		return failure
	}
	if failure, ok := classifyRetriableEngineCondition(err); ok {
		return failure
	}
	var internal interface{ InternalCondition() bool }
	if errors.As(err, &internal) && internal.InternalCondition() {
		return &semantic.Failure{Category: semantic.FailureInternal}
	}
	var notFound *appErrors.NotFoundError
	if errors.As(err, &notFound) {
		return &semantic.Failure{Category: semantic.FailureMissing}
	}
	var statuser interface{ HTTPStatus() int }
	if errors.As(err, &statuser) {
		switch statuser.HTTPStatus() {
		case http.StatusBadRequest:
			return &semantic.Failure{Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentField}
		case http.StatusUnauthorized:
			return &semantic.Failure{Category: semantic.FailureUnauthenticated}
		case http.StatusForbidden:
			return &semantic.Failure{Category: semantic.FailureForbidden}
		case http.StatusNotFound:
			return &semantic.Failure{Category: semantic.FailureMissing}
		}
	}
	return &semantic.Failure{Category: semantic.FailureInternal}
}

func classifyAttachmentSourceEngineError(err error) error {
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return err
	}
	var failure *attachmentsemantic.Failure
	if errors.As(err, &failure) {
		return failure
	}
	if failure, ok := classifyRetriableEngineCondition(err); ok {
		return failure
	}
	var internal interface{ InternalCondition() bool }
	if errors.As(err, &internal) && internal.InternalCondition() {
		return &attachmentsemantic.Failure{Category: attachmentsemantic.FailureInternal}
	}
	var statuser interface{ HTTPStatus() int }
	if errors.As(err, &statuser) {
		switch statuser.HTTPStatus() {
		case http.StatusUnauthorized:
			return &attachmentsemantic.Failure{Category: attachmentsemantic.FailureUnauthenticated}
		case http.StatusForbidden:
			return &attachmentsemantic.Failure{Category: attachmentsemantic.FailureForbidden}
		case http.StatusNotFound:
			return &attachmentsemantic.Failure{Category: attachmentsemantic.FailureMissing}
		}
	}
	return &attachmentsemantic.Failure{Category: attachmentsemantic.FailureInternal}
}
