using CashLineERP.Application.Integration;
using CashLineERP.Contracts.Integration;
using CashLineERP.Contracts.Internal;
using System.Text.Json;

namespace CashLineERP.Application.Internal;

public sealed class InternalWorkbenchService : IInternalWorkbenchService
{
    private readonly IInternalWorkbenchRepository _repository;
    private readonly IOutboxEventService? _outboxEventService;
    private readonly IInboxMessageService? _inboxMessageService;
    private readonly string _repositoryRootPath;

    public InternalWorkbenchService(
        IInternalWorkbenchRepository repository,
        IOutboxEventService? outboxEventService = null,
        IInboxMessageService? inboxMessageService = null,
        string? repositoryRootPath = null)
    {
        _repository = repository;
        _outboxEventService = outboxEventService;
        _inboxMessageService = inboxMessageService;
        _repositoryRootPath = RepositoryRootLocator.Resolve(repositoryRootPath);
    }

    public InternalDashboardSummaryResponse GetDashboardSummary()
    {
        var summary = _repository.GetDashboardSummary();
        return new InternalDashboardSummaryResponse(
            summary.PendingApprovalCount,
            summary.ProcurementApprovalCount,
            summary.FinanceApprovalCount,
            summary.SalesApprovalCount,
            summary.InventoryApprovalCount,
            summary.ReconciliationExceptionCount,
            summary.EtaAttentionCount,
            summary.CreditHoldCount,
            summary.TotalExceptionCount);
    }

    public InternalOperationalDashboardResponse GetOperationalDashboard(string environmentName, string buildVersion)
    {
        var summary = _repository.GetOperationalDashboard();
        return new InternalOperationalDashboardResponse(
            DateTime.UtcNow,
            "OK",
            environmentName,
            buildVersion,
            summary.PendingApprovalCount,
            summary.TotalExceptionCount,
            summary.OpenReconciliationExceptionCount,
            summary.EtaAttentionCount,
            summary.CreditHoldCount,
            summary.OpenSettlementExceptionCount,
            summary.PendingOutboxCount,
            summary.RetryOutboxCount,
            summary.DeadLetterOutboxCount,
            summary.ReceivedInboxCount,
            summary.RetryInboxCount,
            summary.DeadLetterInboxCount,
            summary.PendingNotificationCount,
            summary.RetryNotificationCount,
            summary.FailedNotificationCount,
            summary.DeadLetterNotificationCount,
            summary.OverdueCollectionsCount,
            summary.CriticalAlertCount,
            summary.HighAlertCount,
            summary.WarningAlertCount);
    }

    public InternalWorkflowMonitoringResponse GetWorkflowMonitoring(string environmentName, string buildVersion, int itemLimit = 10)
    {
        var normalizedLimit = Math.Clamp(itemLimit, 1, 25);
        var dashboard = GetOperationalDashboard(environmentName, buildVersion);

        return new InternalWorkflowMonitoringResponse(
            DateTime.UtcNow,
            dashboard.ServiceStatus,
            environmentName,
            buildVersion,
            15,
            dashboard,
            GetOperationalAlerts().Take(normalizedLimit).ToArray(),
            GetApprovalQueue().Take(normalizedLimit).ToArray(),
            GetExceptionQueue().Take(normalizedLimit).ToArray(),
            BuildOutboxBacklog(normalizedLimit),
            BuildInboxBacklog(normalizedLimit));
    }

    public InternalHypercareConsoleResponse GetHypercareConsole(string environmentName, string buildVersion)
    {
        var dashboard = _repository.GetOperationalDashboard();
        var incidents = BuildHypercareIncidents(null, null);
        var releaseMetadata = ReadReleaseMetadata();
        var readiness = ReadReadinessSnapshot();
        var incidentSummary = SummarizeIncidents(incidents);

        return new InternalHypercareConsoleResponse(
            DateTime.UtcNow,
            readiness.OverallStatus ?? "ACTIVE",
            environmentName,
            buildVersion,
            releaseMetadata.ReleaseVersion ?? "UNVERSIONED",
            releaseMetadata.ApiVersion ?? "v1",
            releaseMetadata.ReleaseChannel ?? "UNSPECIFIED",
            releaseMetadata.SupportStatus ?? "ACTIVE",
            readiness.OverallStatus ?? "NOT_EVALUATED",
            readiness.PassedChecks,
            readiness.FailedChecks,
            dashboard.PendingApprovalCount,
            dashboard.TotalExceptionCount,
            incidentSummary.CriticalCount,
            incidentSummary.HighCount,
            incidentSummary.WarningCount,
            incidents.Count,
            GetHypercareRunbooks().Count,
            dashboard.DeadLetterOutboxCount,
            dashboard.DeadLetterInboxCount,
            dashboard.DeadLetterNotificationCount,
            dashboard.OpenSettlementExceptionCount,
            dashboard.OverdueCollectionsCount,
            readiness.RelativePath);
    }

    public IReadOnlyCollection<InternalApprovalQueueItemResponse> GetApprovalQueue(string? moduleCode = null) =>
        _repository.GetApprovalQueue(moduleCode).Select(item => new InternalApprovalQueueItemResponse(
            item.WorkflowInstanceId,
            item.ModuleCode,
            item.DocumentType,
            item.DocumentId,
            item.DocumentNumber,
            item.CurrentStatus,
            item.SubmittedBy,
            item.SubmittedAtUtc,
            item.BranchId,
            item.PartyCode,
            item.Amount)).ToArray();

    public IReadOnlyCollection<InternalExceptionQueueItemResponse> GetExceptionQueue(string? sourceType = null) =>
        _repository.GetExceptionQueue(sourceType).Select(item => new InternalExceptionQueueItemResponse(
            item.SourceType,
            item.ExceptionCategory,
            item.DocumentType,
            item.DocumentId,
            item.DocumentNumber,
            item.BranchId,
            item.PartyCode,
            item.CurrentStatus,
            item.BusinessDate,
            item.Amount,
            item.Summary,
            item.ReferenceText)).ToArray();

    public IReadOnlyCollection<InternalOperationalAlertResponse> GetOperationalAlerts(string? severityCode = null, string? sourceArea = null) =>
        _repository.GetOperationalAlerts(severityCode, sourceArea).Select(item => new InternalOperationalAlertResponse(
            item.SourceArea,
            item.SeverityCode,
            item.AlertCode,
            item.Title,
            item.Summary,
            item.CurrentStatus,
            item.MetricValue,
            item.ThresholdValue,
            item.ReferenceText,
            item.ActionPath)).ToArray();

    public IReadOnlyCollection<InternalHypercareIncidentResponse> GetHypercareIncidents(string? severityCode = null, string? sourceArea = null) =>
        BuildHypercareIncidents(severityCode, sourceArea)
            .Select(item => new InternalHypercareIncidentResponse(
                item.IncidentCode,
                item.SourceArea,
                item.SeverityCode,
                item.CurrentStatus,
                item.Title,
                item.Summary,
                item.MetricValue,
                item.ThresholdValue,
                item.ReferenceText,
                item.ActionPath,
                item.RecommendedRunbookCode,
                item.RecommendedRunbookTitle))
            .ToArray();

    public IReadOnlyCollection<InternalHypercareRunbookResponse> GetHypercareRunbooks(string? categoryCode = null)
    {
        var normalizedCategoryCode = string.IsNullOrWhiteSpace(categoryCode) ? null : categoryCode.Trim().ToUpperInvariant();
        return HypercareRunbookCatalog.Definitions
            .Where(item => normalizedCategoryCode is null || string.Equals(item.CategoryCode, normalizedCategoryCode, StringComparison.OrdinalIgnoreCase))
            .Select(BuildRunbookView)
            .Select(item => new InternalHypercareRunbookResponse(
                item.RunbookCode,
                item.Title,
                item.CategoryCode,
                item.Summary,
                item.RelativePath,
                item.StatusCode,
                item.LastModifiedAtUtc,
                item.CommandHints))
            .ToArray();
    }

    public InternalHypercareRunbookDetailResponse? GetHypercareRunbook(string runbookCode)
    {
        var definition = HypercareRunbookCatalog.Definitions
            .SingleOrDefault(item => string.Equals(item.RunbookCode, runbookCode?.Trim(), StringComparison.OrdinalIgnoreCase));
        if (definition is null)
        {
            return null;
        }

        var view = BuildRunbookDetailView(definition);
        return new InternalHypercareRunbookDetailResponse(
            view.RunbookCode,
            view.Title,
            view.CategoryCode,
            view.Summary,
            view.RelativePath,
            view.StatusCode,
            view.LastModifiedAtUtc,
            view.CommandHints,
            view.MarkdownContent);
    }

    private IReadOnlyCollection<InternalHypercareIncidentView> BuildHypercareIncidents(string? severityCode, string? sourceArea)
    {
        var normalizedSeverityCode = string.IsNullOrWhiteSpace(severityCode) ? null : severityCode.Trim().ToUpperInvariant();
        var normalizedSourceArea = string.IsNullOrWhiteSpace(sourceArea) ? null : sourceArea.Trim().ToUpperInvariant();

        var incidents = _repository.GetOperationalAlerts(normalizedSeverityCode, normalizedSourceArea)
            .Select(item =>
            {
                var recommendation = ResolveRecommendedRunbook(item.SourceArea, item.AlertCode);
                return new InternalHypercareIncidentView(
                    item.AlertCode,
                    item.SourceArea,
                    item.SeverityCode,
                    item.CurrentStatus,
                    item.Title,
                    item.Summary,
                    item.MetricValue,
                    item.ThresholdValue,
                    item.ReferenceText,
                    item.ActionPath,
                    recommendation?.RunbookCode,
                    recommendation?.Title);
            })
            .ToList();

        incidents.AddRange(GetReadinessIncidents(normalizedSeverityCode, normalizedSourceArea));

        return incidents
            .OrderBy(item => SeverityRank(item.SeverityCode))
            .ThenBy(item => item.SourceArea)
            .ThenBy(item => item.IncidentCode)
            .ToArray();
    }

    private IReadOnlyCollection<InternalHypercareIncidentView> GetReadinessIncidents(string? severityCode, string? sourceArea)
    {
        var readiness = ReadReadinessSnapshot();
        if (readiness.State == ReadinessState.NotFound)
        {
            if ((severityCode is not null && !string.Equals(severityCode, "CRITICAL", StringComparison.OrdinalIgnoreCase))
                || (sourceArea is not null && !string.Equals(sourceArea, "OPERATIONS", StringComparison.OrdinalIgnoreCase)))
            {
                return Array.Empty<InternalHypercareIncidentView>();
            }

            return
            [
                new InternalHypercareIncidentView(
                    "READINESS_REPORT_MISSING",
                    "OPERATIONS",
                    "CRITICAL",
                    "OPEN",
                    "Production-readiness report missing",
                    "The hypercare console could not find the latest repository-owned production-readiness report.",
                    1m,
                    0m,
                    "Run the cutover readiness automation to regenerate the report.",
                    null,
                    "CUTOVER",
                    "Cutover Runbook")
            ];
        }

        return readiness.FailedCheckIncidents
            .Where(item => severityCode is null || string.Equals(item.SeverityCode, severityCode, StringComparison.OrdinalIgnoreCase))
            .Where(item => sourceArea is null || string.Equals(item.SourceArea, sourceArea, StringComparison.OrdinalIgnoreCase))
            .Select(item =>
            {
                var recommendation = ResolveRecommendedRunbook(item.SourceArea, item.IncidentCode);
                return new InternalHypercareIncidentView(
                    item.IncidentCode,
                    item.SourceArea,
                    item.SeverityCode,
                    "OPEN",
                    item.Title,
                    item.Summary,
                    1m,
                    0m,
                    item.ReferenceText,
                    null,
                    recommendation?.RunbookCode,
                    recommendation?.Title);
            })
            .ToArray();
    }

    private InternalHypercareRunbookView BuildRunbookView(HypercareRunbookDefinition definition)
    {
        var fullPath = Path.Combine(_repositoryRootPath, definition.RelativePath);
        var exists = File.Exists(fullPath);
        return new InternalHypercareRunbookView(
            definition.RunbookCode,
            definition.Title,
            definition.CategoryCode,
            definition.Summary,
            definition.RelativePath,
            exists ? "AVAILABLE" : "MISSING",
            exists ? File.GetLastWriteTimeUtc(fullPath) : null,
            definition.CommandHints);
    }

    private InternalHypercareRunbookDetailView BuildRunbookDetailView(HypercareRunbookDefinition definition)
    {
        var view = BuildRunbookView(definition);
        var fullPath = Path.Combine(_repositoryRootPath, definition.RelativePath);
        var markdownContent = File.Exists(fullPath)
            ? File.ReadAllText(fullPath)
            : $"# Missing Runbook{Environment.NewLine}{Environment.NewLine}Expected path `{definition.RelativePath}` was not found from the resolved repository root.";

        return new InternalHypercareRunbookDetailView(
            view.RunbookCode,
            view.Title,
            view.CategoryCode,
            view.Summary,
            view.RelativePath,
            view.StatusCode,
            view.LastModifiedAtUtc,
            view.CommandHints,
            markdownContent);
    }

    private ReleaseMetadata ReadReleaseMetadata()
    {
        var path = Path.Combine(_repositoryRootPath, "ops/release/release-governance.json");
        if (!File.Exists(path))
        {
            return new ReleaseMetadata(null, null, null, null);
        }

        using var document = JsonDocument.Parse(File.ReadAllText(path));
        var root = document.RootElement;
        return new ReleaseMetadata(
            root.TryGetProperty("releaseVersion", out var releaseVersion) ? releaseVersion.GetString() : null,
            root.TryGetProperty("apiVersion", out var apiVersion) ? apiVersion.GetString() : null,
            root.TryGetProperty("releaseChannel", out var releaseChannel) ? releaseChannel.GetString() : null,
            root.TryGetProperty("supportStatus", out var supportStatus) ? supportStatus.GetString() : null);
    }

    private ReadinessSnapshot ReadReadinessSnapshot()
    {
        var relativePath = "artifacts/cutover/readiness/production-readiness-report.json";
        var path = Path.Combine(_repositoryRootPath, relativePath);
        if (!File.Exists(path))
        {
            return new ReadinessSnapshot(ReadinessState.NotFound, null, 0, 0, []);
        }

        using var document = JsonDocument.Parse(File.ReadAllText(path));
        var root = document.RootElement;
        var summary = root.TryGetProperty("summary", out var summaryElement) ? summaryElement : default;
        var checks = root.TryGetProperty("checks", out var checksElement) ? checksElement : default;

        var failedChecks = new List<ReadinessIncident>();
        if (checks.ValueKind == JsonValueKind.Array)
        {
            foreach (var check in checks.EnumerateArray())
            {
                if (!check.TryGetProperty("status", out var statusElement)
                    || !string.Equals(statusElement.GetString(), "FAIL", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                var category = check.TryGetProperty("category", out var categoryElement)
                    ? categoryElement.GetString() ?? "OPERATIONS"
                    : "OPERATIONS";
                var code = check.TryGetProperty("code", out var codeElement)
                    ? codeElement.GetString() ?? "READINESS_CHECK_FAILED"
                    : "READINESS_CHECK_FAILED";
                var label = check.TryGetProperty("label", out var labelElement)
                    ? labelElement.GetString() ?? code
                    : code;
                var detail = check.TryGetProperty("detail", out var detailElement)
                    ? detailElement.GetString() ?? "Readiness check failed."
                    : "Readiness check failed.";
                var checkPath = check.TryGetProperty("path", out var pathElement)
                    ? pathElement.GetString()
                    : null;

                failedChecks.Add(new ReadinessIncident(
                    code,
                    NormalizeReadinessSourceArea(category),
                    MapReadinessSeverity(category),
                    label,
                    $"{label} is failing in the current repository-owned readiness snapshot.",
                    checkPath,
                    detail));
            }
        }

        return new ReadinessSnapshot(
            ReadinessState.Available,
            root.TryGetProperty("overallStatus", out var overallStatusElement) ? overallStatusElement.GetString() : null,
            summary.ValueKind == JsonValueKind.Object && summary.TryGetProperty("passedChecks", out var passedChecksElement) ? passedChecksElement.GetInt32() : 0,
            summary.ValueKind == JsonValueKind.Object && summary.TryGetProperty("failedChecks", out var failedChecksElement) ? failedChecksElement.GetInt32() : 0,
            failedChecks,
            relativePath);
    }

    private static IncidentSummary SummarizeIncidents(IReadOnlyCollection<InternalHypercareIncidentView> incidents) =>
        new(
            incidents.Count(item => string.Equals(item.SeverityCode, "CRITICAL", StringComparison.OrdinalIgnoreCase)),
            incidents.Count(item => string.Equals(item.SeverityCode, "HIGH", StringComparison.OrdinalIgnoreCase)),
            incidents.Count(item => string.Equals(item.SeverityCode, "WARNING", StringComparison.OrdinalIgnoreCase)));

    private static HypercareRunbookDefinition? ResolveRecommendedRunbook(string sourceArea, string incidentCode)
    {
        var runbookCode = sourceArea.ToUpperInvariant() switch
        {
            "INTEGRATION" => "INTEGRATION_RECOVERY",
            "NOTIFICATIONS" => "BACKGROUND_WORKERS",
            "DATABASE" => "FLYWAY_OPERATIONS",
            "PUBLICATION" => "DEVELOPER_PORTAL_PUBLICATION",
            "RELEASE" => "CUTOVER",
            "OPERATIONS" when incidentCode.StartsWith("READINESS_", StringComparison.OrdinalIgnoreCase) => "CUTOVER",
            _ => "HYPERCARE_TRIAGE"
        };

        return HypercareRunbookCatalog.Definitions.SingleOrDefault(item => item.RunbookCode == runbookCode);
    }

    private static string NormalizeReadinessSourceArea(string category) => category.ToUpperInvariant() switch
    {
        "DATABASE" => "DATABASE",
        "PUBLICATION" => "PUBLICATION",
        "RELEASE" => "RELEASE",
        _ => "OPERATIONS"
    };

    private static string MapReadinessSeverity(string category) => category.ToUpperInvariant() switch
    {
        "DATABASE" => "HIGH",
        "PUBLICATION" => "HIGH",
        "RELEASE" => "CRITICAL",
        _ => "WARNING"
    };

    private static int SeverityRank(string severityCode) => severityCode.ToUpperInvariant() switch
    {
        "CRITICAL" => 0,
        "HIGH" => 1,
        "WARNING" => 2,
        _ => 3
    };

    private IReadOnlyCollection<OutboxEventResponse> BuildOutboxBacklog(int itemLimit)
    {
        if (_outboxEventService is null)
        {
            return Array.Empty<OutboxEventResponse>();
        }

        return new[] { "DEAD_LETTER", "RETRY", "PENDING" }
            .SelectMany(statusCode => _outboxEventService.GetOutboxEvents(statusCode))
            .OrderBy(item => BacklogStatusRank(item.StatusCode))
            .ThenByDescending(item => item.CreatedAtUtc)
            .DistinctBy(item => item.OutboxEventId)
            .Take(itemLimit)
            .ToArray();
    }

    private IReadOnlyCollection<InboxMessageResponse> BuildInboxBacklog(int itemLimit)
    {
        if (_inboxMessageService is null)
        {
            return Array.Empty<InboxMessageResponse>();
        }

        return new[] { "DEAD_LETTER", "RETRY", "RECEIVED" }
            .SelectMany(statusCode => _inboxMessageService.GetInboxMessages(statusCode))
            .OrderBy(item => BacklogStatusRank(item.StatusCode))
            .ThenByDescending(item => item.CreatedAtUtc)
            .DistinctBy(item => item.InboxMessageId)
            .Take(itemLimit)
            .ToArray();
    }

    private static int BacklogStatusRank(string? statusCode) => statusCode?.Trim().ToUpperInvariant() switch
    {
        "DEAD_LETTER" => 0,
        "RETRY" => 1,
        "PENDING" => 2,
        "RECEIVED" => 2,
        _ => 3
    };

    private sealed record ReleaseMetadata(
        string? ReleaseVersion,
        string? ApiVersion,
        string? ReleaseChannel,
        string? SupportStatus);

    private sealed record ReadinessSnapshot(
        ReadinessState State,
        string? OverallStatus,
        int PassedChecks,
        int FailedChecks,
        IReadOnlyCollection<ReadinessIncident> FailedCheckIncidents,
        string? RelativePath = null);

    private sealed record ReadinessIncident(
        string IncidentCode,
        string SourceArea,
        string SeverityCode,
        string Title,
        string Summary,
        string? ReferenceText,
        string? Detail);

    private sealed record IncidentSummary(int CriticalCount, int HighCount, int WarningCount);

    private enum ReadinessState
    {
        Available,
        NotFound
    }
}
