package runtimeapi

import (
	"context"
	"errors"
	"time"

	"github.com/nexuscrm/shared/pkg/constants"
	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	auditsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/auditlogv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
)

// AuditLogReadServer is the app-blind privileged read boundary for the durable
// audit log. Product routes and response aliases stay in their L1 dialects.
type AuditLogReadServer struct {
	runtimev1.UnimplementedAuditLogReadServiceServer
	reader      sharedcontract.AuditLog
	permissions EffectivePermissionEngine
}

func NewAuditLogReadServer(
	reader sharedcontract.AuditLog,
	permissions EffectivePermissionEngine,
) (*AuditLogReadServer, error) {
	if reader == nil || permissions == nil {
		return nil, errors.New("runtime audit-log read requires audit and permission engines")
	}
	return &AuditLogReadServer{reader: reader, permissions: permissions}, nil
}

func (server *AuditLogReadServer) ReadAuditLog(
	ctx context.Context,
	wireRequest *runtimev1.ReadAuditLogRequest,
) (*runtimev1.ReadAuditLogResponse, error) {
	request, err := mapping.FromWireReadAuditLogRequest(ctx, wireRequest)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, auditsemantic.CapabilityName)
	}
	scope, scopeOK := runtimeExecutionScopeFromContext(ctx)
	trusted, trustedOK := trustedCallFromContext(ctx)
	if !scopeOK || !trustedOK {
		return nil, auditLogStatus(&auditsemantic.Failure{Category: auditsemantic.FailureUnavailable})
	}
	if err := requireCatalogRevision(request.MinCatalogRevision, scope.receipts.CatalogRevision); err != nil {
		return nil, auditLogStatus(err)
	}
	allowed, err := server.hasViewFieldAuditTrail(ctx, trusted)
	if err != nil {
		return nil, auditLogStatus(err)
	}
	if !allowed {
		return nil, auditLogStatus(&auditsemantic.Failure{Category: auditsemantic.FailureForbidden})
	}

	user := trusted.UserSession()
	result, err := server.reader.QueryAuditLog(ctx, sharedcontract.AuditLogQueryRequest{
		ObjectAPIName: request.ObjectID,
		RecordID:      request.RecordID,
		Limit:         int(request.Limit),
		Offset:        int(request.Offset),
	}, &user)
	if err != nil {
		return nil, auditLogStatus(classifyAuditLogReadError(ctx, err))
	}
	if result == nil || result.Object != request.ObjectID ||
		result.Limit != int(request.Limit) || result.Offset != int(request.Offset) {
		return nil, auditLogStatus(&auditsemantic.Failure{Category: auditsemantic.FailureInternal})
	}

	rows := make([]auditsemantic.Row, 0, len(result.Rows))
	for _, row := range result.Rows {
		if !row.ChangedAtValid {
			return nil, auditLogStatus(&auditsemantic.Failure{Category: auditsemantic.FailureInternal})
		}
		rows = append(rows, auditsemantic.Row{
			ID: row.ID, ObjectID: row.ObjectAPIName, RecordID: row.RecordID,
			FieldID: row.FieldName, OldValue: row.OldValue, NewValue: row.NewValue,
			ChangedByID: row.ChangedByID, ChangedAt: row.ChangedAt.UTC().Format(time.RFC3339Nano),
			Action: stringValue(row.Action), ChangesJSON: stringValue(row.ChangesJSON),
			Entrypoint: stringValue(row.Entrypoint), EntrypointDetail: stringValue(row.EntrypointDetail),
			RequestID: stringValue(row.RequestID), TraceID: stringValue(row.TraceID),
			SessionID: stringValue(row.SessionID), ClientIP: stringValue(row.ClientIP),
			ActualUserID: stringValue(row.ActualUserID), Outcome: stringValue(row.Outcome),
			ErrorCode: stringValue(row.ErrorCode),
		})
	}
	wireResponse, err := mapping.ToWireReadAuditLogResponse(request, auditsemantic.Response{
		Rows:     rows,
		Receipts: receiptsForCapability(scope.receipts, auditsemantic.CapabilityName),
	})
	if err != nil {
		return nil, auditLogStatus(err)
	}
	return wireResponse, nil
}

func (server *AuditLogReadServer) hasViewFieldAuditTrail(
	ctx context.Context,
	trusted TrustedCall,
) (bool, error) {
	user := trusted.UserSession()
	result, err := server.permissions.EffectivePermissions(ctx, &user, "", trusted.PermissionFloor())
	if err != nil {
		return false, classifyEffectivePermissionError(ctx, err)
	}
	if result == nil {
		return false, &auditsemantic.Failure{Category: auditsemantic.FailureInternal}
	}
	return result.SystemPermissions[string(constants.PermViewFieldAuditTrail)], nil
}

func classifyAuditLogReadError(ctx context.Context, err error) error {
	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
		return err
	}
	var typed *sharedcontract.AuditLogError
	if errors.As(err, &typed) {
		if typed.Kind == sharedcontract.AuditLogErrorUnavailable {
			return &auditsemantic.Failure{Category: auditsemantic.FailureUnavailable}
		}
	}
	return &auditsemantic.Failure{Category: auditsemantic.FailureInternal}
}

func auditLogStatus(err error) error {
	return mapping.ToPlatformStatusError(err, auditsemantic.CapabilityName)
}

func stringValue(value *string) string {
	if value == nil {
		return ""
	}
	return *value
}
