package runtimeapi

import (
	"context"
	"errors"
	"net/http"
	"sort"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/telemetry"
	"github.com/nexuscrm/records"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	accesssemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordsv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
)

// SelectedRootReader is the real current ContractService read adapter seam.
type SelectedRootReader interface {
	ReadSelectedRecord(context.Context, string, string, []string, *models.UserSession) (*records.SelectedRootRecord, error)
}

// RecordAccessEvaluator is the real current ContractService coarse access
// adapter seam. It deliberately is not added to contract.Deps or the legacy
// three-method RecordAccessReader interface.
type RecordAccessEvaluator interface {
	EvaluateRecordAccess(context.Context, *string, []string, *models.UserSession) (map[string]contract.RecordAccess, error)
}

// RecordReadAccessEvaluator is the narrow existence-masked read authority used
// by consumers that do not need mutation or lock decisions.
type RecordReadAccessEvaluator interface {
	EvaluateRecordReadAccess(context.Context, *string, []string, *models.UserSession) (map[string]contract.RecordAccess, error)
}

type RecordsReader interface {
	SelectedRootReader
	RecordAccessEvaluator
}

// ReceiptSource remains only for historical non-serving/unit compositions.
// D1 production reads receipts exclusively from RuntimeExecutionScope.
type ReceiptSource interface {
	Receipts(context.Context) (semantic.Receipts, error)
}

type ReceiptSourceFunc func(context.Context) (semantic.Receipts, error)

func (f ReceiptSourceFunc) Receipts(ctx context.Context) (semantic.Receipts, error) { return f(ctx) }

// BootstrapReceipts is honest P3 skeleton state: protocol/capability are real,
// while revision/generation axes remain zero until P7 binds their authority.
func BootstrapReceipts(context.Context) (semantic.Receipts, error) {
	return semantic.Receipts{
		ProtocolVersion: "1.0", CapabilityVersion: semantic.CapabilityName,
	}, nil
}

// RecordsServer maps the generated service to the real selected-root engine.
type RecordsServer struct {
	runtimev1.UnimplementedRecordsServiceServer
	reader   RecordsReader
	receipts ReceiptSource
	metrics  *recordsMetrics
}

func NewRecordsServer(reader RecordsReader, receipts ReceiptSource) (*RecordsServer, error) {
	if reader == nil {
		return nil, errors.New("runtime-api: reader is required")
	}
	return &RecordsServer{reader: reader, receipts: receipts, metrics: newRecordsMetrics(nil)}, nil
}

func (s *RecordsServer) ReadSelectedRoot(ctx context.Context, wireRequest *runtimev1.ReadSelectedRootRequest) (*runtimev1.ReadSelectedRootResponse, error) {
	trusted, ok := trustedCallFromContext(ctx)
	if !ok {
		return nil, mapping.ToStatusError(&semantic.Failure{Category: semantic.FailureUnauthenticated})
	}
	user := trusted.UserSession()
	request, err := mapping.FromWireRequest(ctx, wireRequest)
	if err != nil {
		return nil, mapping.ToStatusError(err)
	}
	receipts, err := s.executionReceipts(ctx)
	if err != nil {
		return nil, mapping.ToStatusError(err)
	}
	receipts = receiptsForCapability(receipts, semantic.CapabilityName)
	if request.MinCatalogRevision != nil && *request.MinCatalogRevision > receipts.CatalogRevision {
		return nil, mapping.ToStatusError(&semantic.Failure{
			Category: semantic.FailureStaleState, Receipts: &receipts,
			StaleAxis: semantic.StaleAxisCatalog, RequiredRevision: *request.MinCatalogRevision,
			ServedRevision: receipts.CatalogRevision,
		})
	}

	repositoryCtx, sqlCounter := telemetry.WithSQLStatementCounter(ctx)
	record, err := s.reader.ReadSelectedRecord(repositoryCtx, request.ObjectID, request.RecordID, request.SelectedRootFields, &user)
	s.metrics.observeRepository(recordsOperationSelectedRoot, sqlCounter.Count())
	if err != nil {
		return nil, mapping.ToStatusError(classifyEngineError(err))
	}
	response := semantic.Response{RecordID: record.RecordID, Receipts: receipts, Fields: make([]semantic.Field, 0, len(record.Fields))}
	for _, field := range record.Fields {
		response.Fields = append(response.Fields, semanticField(field))
	}
	wireResponse, err := mapping.ToWireResponse(response)
	if err != nil {
		return nil, mapping.ToStatusError(err)
	}
	return wireResponse, nil
}

func (s *RecordsServer) EvaluateRecordAccess(ctx context.Context, wireRequest *runtimev1.EvaluateRecordAccessRequest) (*runtimev1.EvaluateRecordAccessResponse, error) {
	trusted, ok := trustedCallFromContext(ctx)
	if !ok {
		return nil, mapping.ToRecordAccessStatusError(&accesssemantic.Failure{Category: accesssemantic.FailureUnauthenticated})
	}
	request, err := mapping.FromWireEvaluateRecordAccessRequest(ctx, wireRequest)
	if err != nil {
		return nil, mapping.ToRecordAccessStatusError(err)
	}
	receipts, err := s.executionReceipts(ctx)
	if err != nil {
		return nil, mapping.ToRecordAccessStatusError(err)
	}
	receipts = receiptsForCapability(receipts, accesssemantic.CapabilityName)
	user := trusted.UserSession()
	repositoryCtx, sqlCounter := telemetry.WithSQLStatementCounter(ctx)
	access, err := s.reader.EvaluateRecordAccess(repositoryCtx, request.ObjectID, request.RecordIDs, &user)
	s.metrics.observeRepository(recordsOperationAccess, sqlCounter.Count())
	if err != nil {
		return nil, mapping.ToRecordAccessStatusError(classifyRecordAccessEngineError(err))
	}
	ids := make([]string, 0, len(access))
	for id, decision := range access {
		if decision.Read {
			ids = append(ids, id)
		}
	}
	sort.Strings(ids)
	response := accesssemantic.Response{
		Results: make([]accesssemantic.Result, 0, len(ids)), Receipts: receipts,
	}
	for _, id := range ids {
		decision := access[id]
		response.Results = append(response.Results, accesssemantic.Result{
			RecordID: id,
			Access: accesssemantic.Access{
				Read: decision.Read, Update: decision.Update,
				Delete: decision.Delete, Locked: decision.Locked,
			},
		})
	}
	wireResponse, err := mapping.ToWireEvaluateRecordAccessResponse(request, response)
	if err != nil {
		return nil, mapping.ToRecordAccessStatusError(err)
	}
	return wireResponse, nil
}

func (s *RecordsServer) executionReceipts(ctx context.Context) (semantic.Receipts, error) {
	if scope, ok := runtimeExecutionScopeFromContext(ctx); ok {
		return scope.receipts, nil
	}
	if s.receipts == nil {
		return semantic.Receipts{}, &semantic.Failure{Category: semantic.FailureUnavailable}
	}
	return s.receipts.Receipts(ctx)
}

func receiptsForCapability(receipts semantic.Receipts, capability string) semantic.Receipts {
	receipts.ProtocolVersion = "1.0"
	receipts.CapabilityVersion = capability
	return receipts
}

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

func classifyEngineError(err error) error {
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return err
	}
	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.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentObjectID}
	}
	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 semanticField(field records.SelectedRootField) semantic.Field {
	result := semantic.Field{
		Name: field.Name, Kind: semantic.ValueKind(field.Kind), Null: field.Null,
		String: field.String, Boolean: field.Boolean, Integer: field.Integer,
		Date: field.Date, DateTime: field.DateTime, Reference: field.Reference,
	}
	if field.Decimal != nil {
		result.Decimal = &semantic.Decimal{Coefficient: field.Decimal.Coefficient, Scale: field.Decimal.Scale}
	}
	return result
}
