@page "/GestionZip"
@rendermode @(new InteractiveServerRenderMode(prerender: false))
@attribute [Authorize]
@using System.Globalization
@using GestionaDenunciasAN.Models
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject ApiDenunciasClient ApiDenuncias

<PageTitle>Entrada de denuncias</PageTitle>

<div class="container py-4">
    <div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3 mb-4">
        <div>
            <h3 class="mb-1">Entrada de denuncias</h3>
            <p class="text-muted mb-0">
                La app mantiene su propia sesion activa. Cuando caduque GlobalLeaks, aqui solo hara falta renovar el 2FA
                para seguir trayendo denuncias.
            </p>
        </div>
    </div>

    @if (!string.IsNullOrWhiteSpace(StatusMessage))
    {
        <div class="alert @StatusCss mb-4">@StatusMessage</div>
    }

    @if (!string.IsNullOrWhiteSpace(FilterWarningMessage))
    {
        <div class="alert @FilterWarningCss mb-4">@FilterWarningMessage</div>
    }

    <div class="row g-4">
        <div class="col-lg-4">
            <div class="card shadow-sm h-100">
                <div class="card-body">
                    <h5 class="card-title">Sesion GlobalLeaks</h5>
                    <p class="mb-2"><strong>Usuario:</strong> @CurrentUsername</p>
                    <p class="mb-2"><strong>Estado:</strong> @SessionStatusText</p>
                    <p class="mb-2">
                        <strong>Ultima descarga registrada:</strong>
                        @(UserInboxState.LastDownloadedReportMomentUtc is null
                            ? "Sin descargas previas"
                            : UserInboxState.LastDownloadedReportMomentUtc.Value.ToLocalTime().ToString("dd/MM/yyyy HH:mm"))
                    </p>
                    <p class="text-muted small mb-4">
                        La base de datos lleva la cuenta de lo que ya ha descargado este usuario, de lo que han descargado otros
                        y de si el expediente ya esta creado en Gestiona.
                    </p>

                    <div class="mb-3">
                        <label class="form-label">Nuevo codigo 2FA</label>
                        <input class="form-control"
                               @bind="RenewAuthcode"
                               maxlength="6"
                               inputmode="numeric"
                               placeholder="123456" />
                    </div>

                    <button type="button" class="btn btn-primary w-100" @onclick="RenewSessionAsync" disabled="@RenewBusy">
                        @(RenewBusy ? "Renovando..." : SessionRenewButtonText)
                    </button>
                </div>
            </div>
        </div>

        <div class="col-lg-8">
            <div class="card shadow-sm">
                <div class="card-body">
                    <div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-3">
                        <div>
                            <h5 class="card-title mb-1">Bandeja GlobalLeaks</h5>
                            <p class="text-muted mb-0">
                                Puedes revisar solo lo nuevo, las actualizaciones desde tu ultima descarga o un intervalo de fechas.
                            </p>
                        </div>
                        <button type="button" class="btn btn-outline-primary" @onclick="LoadReportsAsync" disabled="@ReportsBusy || !CanUseGlobalLeaks">
                            @(ReportsBusy ? "Actualizando..." : "Actualizar denuncias")
                        </button>
                    </div>

                    <div class="row g-3 mb-3">
                        <div class="col-md-4">
                            <label class="form-label">Tipo</label>
                            <select class="form-select" value="@Filter" @onchange="OnFilterChanged">
                                <option value="all">Todas</option>
                                <option value="new">Nuevas / sin leer</option>
                                <option value="updated">Actualizaciones</option>
                            </select>
                        </div>

                        <div class="col-md-4">
                            <label class="form-label">Periodo</label>
                            <select class="form-select" value="@DateScope" @onchange="OnDateScopeChanged">
                                <option value="all">Todo el buzon</option>
                                <option value="since_mine">Desde mi ultima descarga</option>
                                <option value="range">Intervalo de fechas</option>
                            </select>
                        </div>

                        <div class="col-md-4">
                            <label class="form-label">Canal</label>
                            <select class="form-select" value="@SelectedChannel" @onchange="OnChannelChanged">
                                <option value="">Todos</option>
                                @foreach (var context in Contexts)
                                {
                                    <option value="@context.Id">@context.Name</option>
                                }
                            </select>
                        </div>

                        <div class="col-md-3">
                            <label class="form-label">Desde</label>
                            <input class="form-control"
                                   type="date"
                                   value="@DateFrom"
                                   disabled="@(DateScope != "range")"
                                   @onchange="OnDateFromChanged" />
                        </div>

                        <div class="col-md-3">
                            <label class="form-label">Hasta</label>
                            <input class="form-control"
                                   type="date"
                                   value="@DateTo"
                                   disabled="@(DateScope != "range")"
                                   @onchange="OnDateToChanged" />
                        </div>

                        <div class="col-md-6">
                            <label class="form-label">Buscar</label>
                            <input class="form-control"
                                   value="@SearchTerm"
                                   @oninput="OnSearchChanged"
                                   placeholder="# denuncia o canal" />
                        </div>
                    </div>

                    <div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3 mb-3">
                        <div class="text-muted">
                            @VisibleReports.Count denuncia(s) visibles, @SelectedReportsCount seleccionada(s).
                            @if (UserInboxState.LastDownloadedReportMomentUtc is not null)
                            {
                                <span class="d-block small">
                                    Referencia de ultima descarga: @UserInboxState.LastDownloadedReportMomentUtc.Value.ToLocalTime().ToString("dd/MM/yyyy HH:mm")
                                </span>
                            }
                        </div>
                        <div class="d-flex gap-2">
                            <button type="button" class="btn btn-outline-secondary btn-sm" @onclick="ToggleSelectAll" disabled="@(!VisibleReports.Any())">
                                @SelectAllLabel
                            </button>
                            <button type="button" class="btn btn-success btn-sm" @onclick="ImportSelectedAsync" disabled="@(!CanImportSelected)">
                                @(ImportBusy ? "Importando..." : "Importar seleccionadas")
                            </button>
                        </div>
                    </div>

                    <div class="table-responsive">
                        <table class="table table-hover align-middle">
                            <thead>
                                <tr>
                                    <th style="width: 3rem;"></th>
                                    <th>#</th>
                                    <th>Canal</th>
                                    <th>Presentacion</th>
                                    <th>Ultima actualizacion</th>
                                    <th>Estado</th>
                                    <th>Seguimiento</th>
                                </tr>
                            </thead>
                            <tbody>
                                @if (!CanUseGlobalLeaks)
                                {
                                    <tr>
                                        <td colspan="7" class="text-muted">
                                            Renueva la sesion de GlobalLeaks con un 2FA valido para cargar la bandeja.
                                        </td>
                                    </tr>
                                }
                                else if (ReportsBusy)
                                {
                                    <tr>
                                        <td colspan="7" class="text-muted">Cargando denuncias...</td>
                                    </tr>
                                }
                                else if (!VisibleReports.Any())
                                {
                                    <tr>
                                        <td colspan="7" class="text-muted">No hay denuncias con los filtros actuales.</td>
                                    </tr>
                                }
                                else
                                {
                                    @foreach (var report in VisibleReports)
                                    {
                                        <tr @key="report.Id" class="@GetReportRowCss(report)">
                                            <td>
                                                <input type="checkbox"
                                                       checked="@SelectedIds.Contains(report.Id)"
                                                       @onchange="@((ChangeEventArgs args) => ToggleSelection(report.Id, args))" />
                                            </td>
                                            <td><strong>#@(report.Progressive ?? 0)</strong></td>
                                            <td>@(report.ContextName ?? report.ContextId ?? "-")</td>
                                            <td>@FormatDate(report.CreationDate)</td>
                                            <td>@FormatDate(report.UpdateDate)</td>
                                            <td>
                                                <span class="badge @GetStatusBadgeCss(report)">
                                                    @GetStatusLabel(report)
                                                </span>
                                            </td>
                                            <td>
                                                <span class="badge @GetTrackingBadgeCss(report)">
                                                    @GetTrackingLabel(report)
                                                </span>
                                                @if (!string.IsNullOrWhiteSpace(report.TrackingNote))
                                                {
                                                    <div class="small text-muted mt-1">@report.TrackingNote</div>
                                                }
                                            </td>
                                        </tr>
                                    }
                                }
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

@code {
    private readonly List<ContextDto> Contexts = [];
    private readonly List<ReportDto> Reports = [];
    private List<ReportDto> VisibleReports = [];
    private HashSet<string> SelectedIds = [];

    private ApiGlobalLeaksSessionDto? SessionInfo;
    private InboxUserState UserInboxState = new();
    private string CurrentUsername { get; set; } = string.Empty;
    private string RenewAuthcode { get; set; } = string.Empty;
    private string Filter { get; set; } = "all";
    private string DateScope { get; set; } = "all";
    private string DateFrom { get; set; } = string.Empty;
    private string DateTo { get; set; } = string.Empty;
    private string SelectedChannel { get; set; } = string.Empty;
    private string SearchTerm { get; set; } = string.Empty;
    private string StatusMessage { get; set; } = string.Empty;
    private string StatusCss { get; set; } = "alert-info";
    private string FilterWarningMessage { get; set; } = string.Empty;
    private string FilterWarningCss { get; set; } = "alert-warning";
    private bool ReportsBusy { get; set; }
    private bool RenewBusy { get; set; }
    private bool ImportBusy { get; set; }

    private bool CanUseGlobalLeaks => SessionInfo?.HasActiveSession == true;
    private int SelectedReportsCount => SelectedIds.Count;
    private bool CanImportSelected => CanUseGlobalLeaks && SelectedReportsCount > 0 && !ImportBusy;
    private string SessionStatusText => SessionInfo is null
        ? "Sin credenciales guardadas"
        : SessionInfo.HasActiveSession
            ? "Activa"
            : "Pendiente de renovacion 2FA";
    private string SessionRenewButtonText => SessionInfo?.HasActiveSession == true
        ? "Renovar sesion"
        : "Activar sesion";
    private string SelectAllLabel => VisibleReports.Count > 0 && VisibleReports.All(report => SelectedIds.Contains(report.Id))
        ? "Deseleccionar todas"
        : "Seleccionar todas";

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        CurrentUsername = authState.User.Identity?.Name ?? string.Empty;

        await LoadSessionStateAsync();

        if (CanUseGlobalLeaks)
        {
            await LoadReportsAsync();
        }
        else if (SessionInfo is not null)
        {
            SetStatus(
                "La aplicacion sigue iniciada, pero la sesion de GlobalLeaks no esta activa. Introduce un nuevo 2FA para renovarla.",
                "alert-warning");
        }
    }

    private async Task LoadSessionStateAsync()
    {
        SessionInfo = string.IsNullOrWhiteSpace(CurrentUsername)
            ? null
            : await ApiDenuncias.GetGlobalLeaksSessionAsync();
    }

    private async Task RenewSessionAsync()
    {
        if (SessionInfo is null)
        {
            SetStatus("No hay credenciales guardadas para este usuario. Cierra sesion y vuelve a entrar.", "alert-danger");
            return;
        }

        if (string.IsNullOrWhiteSpace(RenewAuthcode) || RenewAuthcode.Trim().Length != 6)
        {
            SetStatus("Introduce un codigo 2FA valido de 6 digitos.", "alert-warning");
            return;
        }

        RenewBusy = true;
        try
        {
            SessionInfo = await ApiDenuncias.RenewGlobalLeaksSessionAsync(RenewAuthcode.Trim(), CancellationToken.None);
            RenewAuthcode = string.Empty;
            await LoadReportsAsync();
            SetStatus("Sesion de GlobalLeaks renovada correctamente.", "alert-success");
        }
        catch (Exception ex)
        {
            SetStatus(ex.Message, "alert-danger");
        }
        finally
        {
            RenewBusy = false;
        }
    }

    private async Task LoadReportsAsync()
    {
        if (!CanUseGlobalLeaks)
        {
            return;
        }

        ReportsBusy = true;
        try
        {
            var inbox = await ApiDenuncias.LoadInboxAsync(CancellationToken.None);

            Contexts.Clear();
            Contexts.AddRange(inbox.Contexts);

            Reports.Clear();
            Reports.AddRange(inbox.Reports);
            UserInboxState = inbox.UserState;
            ApplyFilters();
        }
        catch (UnauthorizedAccessException ex)
        {
            await LoadSessionStateAsync();
            Reports.Clear();
            VisibleReports.Clear();
            SelectedIds.Clear();
            SetStatus(ex.Message, "alert-warning");
        }
        catch (Exception ex)
        {
            SetStatus(ex.Message, "alert-danger");
        }
        finally
        {
            ReportsBusy = false;
        }
    }

    private async Task ImportSelectedAsync()
    {
        if (!CanUseGlobalLeaks)
        {
            SetStatus("Renueva antes la sesion de GlobalLeaks para importar denuncias.", "alert-warning");
            return;
        }

        ImportBusy = true;
        var importedCount = 0;
        var errors = new List<string>();

        try
        {
            var selectedReports = Reports
                .Where(report => SelectedIds.Contains(report.Id))
                .OrderBy(report => report.Progressive ?? 0)
                .ToList();

            foreach (var report in selectedReports)
            {
                try
                {
                    var result = await ApiDenuncias.ImportReportAsync(report, CancellationToken.None);
                    importedCount += result.ImportedCount;
                    errors.AddRange(result.Errors.Select(error => $"#{report.Progressive ?? 0}: {error}"));
                }
                catch (UnauthorizedAccessException ex)
                {
                    await LoadSessionStateAsync();
                    SetStatus(ex.Message, "alert-warning");
                    break;
                }
                catch (Exception ex)
                {
                    errors.Add($"#{report.Progressive ?? 0}: {ex.Message}");
                }
            }

            SelectedIds.Clear();
            await LoadReportsAsync();

            var warnings = selectedReports
                .Where(report => report.DownloadedByAnotherUser || report.AlreadyInGestiona)
                .Select(report => $"#{report.Progressive ?? 0}: {report.TrackingNote}")
                .Where(message => !string.IsNullOrWhiteSpace(message))
                .ToList();

            if (errors.Count == 0 && warnings.Count == 0)
            {
                SetStatus($"Se han importado {importedCount} denuncia(s) desde GlobalLeaks.", "alert-success");
            }
            else
            {
                var parts = new List<string>
                {
                    $"Se han importado {importedCount} denuncia(s)."
                };

                if (warnings.Count > 0)
                {
                    parts.Add($"Avisos: {string.Join(" | ", warnings)}");
                }

                if (errors.Count > 0)
                {
                    parts.Add($"Incidencias: {string.Join(" | ", errors)}");
                }

                SetStatus(string.Join(" ", parts), errors.Count == 0 ? "alert-warning" : "alert-danger");
            }
        }
        finally
        {
            ImportBusy = false;
        }
    }

    private void ApplyFilters()
    {
        IEnumerable<ReportDto> filtered = Reports;
        FilterWarningMessage = string.Empty;
        FilterWarningCss = "alert-warning";

        filtered = Filter switch
        {
            "new" => filtered.Where(report => string.IsNullOrWhiteSpace(report.AccessDate) || string.Equals(report.Status, "new", StringComparison.OrdinalIgnoreCase)),
            "updated" => filtered.Where(report => report.Updated),
            _ => filtered
        };

        filtered = ApplyDateScope(filtered);

        if (!string.IsNullOrWhiteSpace(SelectedChannel))
        {
            filtered = filtered.Where(report => string.Equals(report.ContextId, SelectedChannel, StringComparison.OrdinalIgnoreCase));
        }

        if (!string.IsNullOrWhiteSpace(SearchTerm))
        {
            var search = SearchTerm.Trim();
            filtered = filtered.Where(report =>
                (report.Progressive?.ToString() ?? string.Empty).Contains(search, StringComparison.OrdinalIgnoreCase) ||
                (report.ContextName ?? report.ContextId ?? string.Empty).Contains(search, StringComparison.OrdinalIgnoreCase));
        }

        VisibleReports = filtered
            .OrderByDescending(report => report.Progressive ?? 0)
            .ToList();

        var validIds = Reports.Select(report => report.Id).ToHashSet(StringComparer.OrdinalIgnoreCase);
        SelectedIds.RemoveWhere(id => !validIds.Contains(id));
    }

    private IEnumerable<ReportDto> ApplyDateScope(IEnumerable<ReportDto> reports)
    {
        if (DateScope == "since_mine")
        {
            if (UserInboxState.LastDownloadedReportMomentUtc is null)
            {
                FilterWarningMessage = "Este usuario aun no tiene descargas previas. Se muestran todas las denuncias del filtro seleccionado.";
                FilterWarningCss = "alert-info";
                return reports;
            }

            var lastMoment = UserInboxState.LastDownloadedReportMomentUtc.Value;
            return reports.Where(report => GetEffectiveMoment(report) is DateTimeOffset moment && moment > lastMoment);
        }

        if (DateScope == "range")
        {
            var hasFrom = DateOnly.TryParseExact(DateFrom, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var fromDate);
            var hasTo = DateOnly.TryParseExact(DateTo, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var toDate);

            if (hasFrom && hasTo && fromDate > toDate)
            {
                FilterWarningMessage = "La fecha inicial no puede ser posterior a la final.";
                FilterWarningCss = "alert-danger";
                return Enumerable.Empty<ReportDto>();
            }

            var filtered = reports.Where(report =>
            {
                var moment = GetEffectiveMoment(report);
                if (moment is null)
                {
                    return false;
                }

                var reportDate = DateOnly.FromDateTime(moment.Value.UtcDateTime);
                var matchesFrom = !hasFrom || reportDate >= fromDate;
                var matchesTo = !hasTo || reportDate <= toDate;
                return matchesFrom && matchesTo;
            });

            if (hasFrom &&
                UserInboxState.LastDownloadedReportMomentUtc is DateTimeOffset lastDownloaded &&
                fromDate > DateOnly.FromDateTime(lastDownloaded.UtcDateTime))
            {
                var gapExists = Reports.Any(report =>
                {
                    var moment = GetEffectiveMoment(report);
                    if (moment is null)
                    {
                        return false;
                    }

                    var reportDate = DateOnly.FromDateTime(moment.Value.UtcDateTime);
                    return moment.Value > lastDownloaded && reportDate < fromDate;
                });

                if (gapExists)
                {
                    FilterWarningMessage =
                        $"Ojo: entre tu ultima descarga ({lastDownloaded.ToLocalTime():dd/MM/yyyy HH:mm}) y el inicio del intervalo ({fromDate:dd/MM/yyyy}) hay denuncias o actualizaciones sin revisar.";
                }
            }

            return filtered;
        }

        return reports;
    }

    private void ToggleSelection(string reportId, ChangeEventArgs args)
    {
        var isChecked = GetCheckedValue(args);
        if (isChecked)
        {
            SelectedIds.Add(reportId);
        }
        else
        {
            SelectedIds.Remove(reportId);
        }

        StateHasChanged();
    }

    private void ToggleSelectAll()
    {
        var shouldSelect = !VisibleReports.All(report => SelectedIds.Contains(report.Id));

        foreach (var report in VisibleReports)
        {
            if (shouldSelect)
            {
                SelectedIds.Add(report.Id);
            }
            else
            {
                SelectedIds.Remove(report.Id);
            }
        }

        StateHasChanged();
    }

    private void OnFilterChanged(ChangeEventArgs args)
    {
        Filter = args.Value?.ToString() ?? "all";
        ApplyFilters();
    }

    private void OnDateScopeChanged(ChangeEventArgs args)
    {
        DateScope = args.Value?.ToString() ?? "all";
        if (DateScope != "range")
        {
            DateFrom = string.Empty;
            DateTo = string.Empty;
        }

        ApplyFilters();
    }

    private void OnDateFromChanged(ChangeEventArgs args)
    {
        DateFrom = args.Value?.ToString() ?? string.Empty;
        ApplyFilters();
    }

    private void OnDateToChanged(ChangeEventArgs args)
    {
        DateTo = args.Value?.ToString() ?? string.Empty;
        ApplyFilters();
    }

    private void OnChannelChanged(ChangeEventArgs args)
    {
        SelectedChannel = args.Value?.ToString() ?? string.Empty;
        ApplyFilters();
    }

    private void OnSearchChanged(ChangeEventArgs args)
    {
        SearchTerm = args.Value?.ToString() ?? string.Empty;
        ApplyFilters();
    }

    private void SetStatus(string message, string cssClass)
    {
        StatusMessage = message;
        StatusCss = cssClass;
    }

    private static bool GetCheckedValue(ChangeEventArgs args)
    {
        return args.Value switch
        {
            bool b => b,
            string s when bool.TryParse(s, out var parsed) => parsed,
            _ => false
        };
    }

    private static string FormatDate(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return "-";
        }

        return DateTimeOffset.TryParse(value, out var parsed)
            ? parsed.ToLocalTime().ToString("dd/MM/yyyy HH:mm")
            : "-";
    }

    private static DateTimeOffset? GetEffectiveMoment(ReportDto report)
    {
        return ParseDate(report.UpdateDate) ?? ParseDate(report.CreationDate);
    }

    private static DateTimeOffset? ParseDate(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        return DateTimeOffset.TryParse(value.Replace("Z", "+00:00", StringComparison.Ordinal), out var parsed)
            ? parsed
            : null;
    }

    private static string GetStatusLabel(ReportDto report)
    {
        if (string.IsNullOrWhiteSpace(report.AccessDate))
        {
            return "Sin leer";
        }

        if (report.Updated)
        {
            return "Actualizada";
        }

        return string.Equals(report.Status, "closed", StringComparison.OrdinalIgnoreCase)
            ? "Cerrada"
            : "Abierta";
    }

    private static string GetStatusBadgeCss(ReportDto report)
    {
        if (string.IsNullOrWhiteSpace(report.AccessDate))
        {
            return "bg-warning text-dark";
        }

        if (report.Updated)
        {
            return "bg-info text-dark";
        }

        return string.Equals(report.Status, "closed", StringComparison.OrdinalIgnoreCase)
            ? "bg-secondary"
            : "bg-primary";
    }

    private static string GetTrackingLabel(ReportDto report)
    {
        if (report.AlreadyInGestiona)
        {
            return "Expediente creado";
        }

        if (report.DownloadedByAnotherUser)
        {
            return "Descargada por otro";
        }

        if (report.DownloadedByCurrentUser)
        {
            return "Descargada por ti";
        }

        if (report.AlreadyImported)
        {
            return "Ya incorporada";
        }

        return "Pendiente";
    }

    private static string GetTrackingBadgeCss(ReportDto report)
    {
        if (report.AlreadyInGestiona)
        {
            return "bg-success";
        }

        if (report.DownloadedByAnotherUser)
        {
            return "bg-warning text-dark";
        }

        if (report.DownloadedByCurrentUser)
        {
            return "bg-secondary";
        }

        if (report.AlreadyImported)
        {
            return "bg-info text-dark";
        }

        return "bg-light text-dark";
    }

    private static string? GetReportRowCss(ReportDto report)
    {
        if (report.AlreadyInGestiona)
        {
            return "table-success";
        }

        if (report.DownloadedByAnotherUser)
        {
            return "table-warning";
        }

        return null;
    }
}
