using System.Text.Json;
using QuantEngine.Core.Interfaces;

namespace QuantEngine.Application.Services;

/// <summary>
/// Canonical application path for recording factor evidence, decisions, and
/// realized outcomes in the normalized PostgreSQL learning store.
/// </summary>
public sealed class DecisionLearningService
{
    private readonly INormalizedLearningStore _store;

    public DecisionLearningService(INormalizedLearningStore store) => _store = store;

    public async Task<Guid> RecordDecisionAsync(
        string decisionKey,
        DateTimeOffset decidedAt,
        string instrumentId,
        string action,
        string gate,
        decimal? score,
        string sourceVersion,
        IEnumerable<FactorEvidenceInput> factors,
        object? trace = null,
        object? provenance = null)
    {
        decisionKey = RequireValue(decisionKey, nameof(decisionKey));
        instrumentId = RequireValue(instrumentId, nameof(instrumentId));
        action = RequireValue(action, nameof(action));
        gate = RequireValue(gate, nameof(gate));
        sourceVersion = RequireValue(sourceVersion, nameof(sourceVersion));

        var decisionId = await _store.AppendDecisionAsync(new DecisionEventRecord(
            decisionKey,
            decidedAt,
            instrumentId,
            action,
            gate,
            score,
            sourceVersion,
            SerializeJson(trace),
            SerializeJson(provenance)));

        foreach (var factor in factors ?? throw new ArgumentNullException(nameof(factors)))
        {
            var normalizedFactor = NormalizeFactor(factor);
            var observationId = await _store.AppendSourceObservationAsync(new SourceObservationRecord(
                normalizedFactor.ObservedAt,
                instrumentId,
                normalizedFactor.SourceName,
                sourceVersion,
                normalizedFactor.PayloadJson,
                normalizedFactor.ProvenanceJson));
            var factorObservationId = await _store.AppendFactorObservationAsync(new FactorObservationRecord(
                observationId,
                normalizedFactor.FactorObservationId,
                normalizedFactor.FactorId,
                normalizedFactor.FactorVersion,
                normalizedFactor.ObservedAt,
                normalizedFactor.NumericValue,
                normalizedFactor.TextValue,
                normalizedFactor.Gate,
                normalizedFactor.ProvenanceJson));
            await _store.AppendDecisionFactorEvidenceAsync(decisionId, factorObservationId, normalizedFactor.Role);
        }

        return decisionId;
    }

    public Task RecordOutcomeAsync(
        Guid decisionId,
        int horizonDays,
        DateTimeOffset evaluatedAt,
        decimal? realizedReturn,
        decimal? benchmarkReturn,
        string outcomeClass,
        string evaluationGate,
        object? provenance = null)
    {
        decimal? excessReturn = realizedReturn.HasValue && benchmarkReturn.HasValue
            ? realizedReturn.Value - benchmarkReturn.Value
            : null;
        return _store.AppendOutcomeAsync(new OutcomeEvaluationRecord(
            decisionId,
            horizonDays,
            evaluatedAt,
            realizedReturn,
            benchmarkReturn,
            excessReturn,
            outcomeClass,
            evaluationGate,
            SerializeJson(provenance)));
    }

    private static string SerializeJson(object? value)
        => JsonSerializer.Serialize(value ?? new { });

    private static string RequireValue(string value, string parameterName)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentException("Value is required.", parameterName);
        }

        return value.Trim();
    }

    private static FactorEvidenceInput NormalizeFactor(FactorEvidenceInput factor)
    {
        ArgumentNullException.ThrowIfNull(factor);

        return factor with
        {
            FactorId = RequireValue(factor.FactorId, nameof(factor.FactorId)),
            FactorVersion = RequireValue(factor.FactorVersion, nameof(factor.FactorVersion)),
            SourceName = RequireValue(factor.SourceName, nameof(factor.SourceName)),
            PayloadJson = RequireValue(factor.PayloadJson, nameof(factor.PayloadJson)),
            ProvenanceJson = RequireValue(factor.ProvenanceJson, nameof(factor.ProvenanceJson)),
            Gate = RequireValue(factor.Gate, nameof(factor.Gate)),
            Role = RequireValue(factor.Role, nameof(factor.Role))
        };
    }
}

public sealed record FactorEvidenceInput(
    Guid FactorObservationId,
    string FactorId,
    string FactorVersion,
    DateTimeOffset ObservedAt,
    decimal? NumericValue,
    string? TextValue,
    string Gate,
    string Role,
    string SourceName,
    string PayloadJson,
    string ProvenanceJson);
