using System.Globalization;
using System.Text;
using CsvHelper;
using CsvHelper.Configuration;
using Microsoft.EntityFrameworkCore;
using OTSSignsOrchestrator.Server.Data;
using OTSSignsOrchestrator.Server.Data.Entities;

namespace OTSSignsOrchestrator.Server.Reports;

public class BillingReportService
{
    private readonly IServiceProvider _services;
    private readonly ILogger<BillingReportService> _logger;

    public BillingReportService(IServiceProvider services, ILogger<BillingReportService> logger)
    {
        _services = services;
        _logger = logger;
    }

    /// <summary>
    /// Generates a billing CSV for the given date range.
    /// Columns: customer_abbrev, company_name, date, screen_count, plan
    /// </summary>
    public async Task<byte[]> GenerateBillingCsvAsync(DateOnly from, DateOnly to)
    {
        await using var scope = _services.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<OrchestratorDbContext>();

        var rows = await db.ScreenSnapshots
            .AsNoTracking()
            .Include(s => s.Instance)
                .ThenInclude(i => i.Customer)
            .Where(s => s.SnapshotDate >= from && s.SnapshotDate <= to)
            .Select(s => new
            {
                Abbrev = s.Instance.Customer.Abbreviation,
                CompanyName = s.Instance.Customer.CompanyName,
                Plan = s.Instance.Customer.Plan,
                s.SnapshotDate,
                s.ScreenCount,
            })
            .ToListAsync();

        // Group by customer abbreviation + date, sum screen counts across instances
        var grouped = rows
            .GroupBy(r => new { r.Abbrev, r.SnapshotDate })
            .Select(g => new BillingCsvRow
            {
                CustomerAbbrev = g.Key.Abbrev,
                CompanyName = g.First().CompanyName,
                Date = g.Key.SnapshotDate,
                ScreenCount = g.Sum(r => r.ScreenCount),
                Plan = g.First().Plan.ToString(),
            })
            .OrderBy(r => r.CustomerAbbrev)
            .ThenBy(r => r.Date)
            .ToList();

        _logger.LogInformation("Generated billing CSV: {RowCount} rows for {From} to {To}",
            grouped.Count, from, to);

        return WriteCsv(grouped);
    }

    /// <summary>
    /// Generates a version drift CSV showing current vs latest Xibo version and OAuth credential age.
    /// Columns: abbrev, current_version, latest_version, credential_age_days
    /// </summary>
    public async Task<byte[]> GenerateVersionDriftCsvAsync()
    {
        await using var scope = _services.CreateAsyncScope();
        var db = scope.ServiceProvider.GetRequiredService<OrchestratorDbContext>();

        // Latest health event per instance where check_name = "xibo-version"
        var versionEvents = await db.HealthEvents
            .AsNoTracking()
            .Include(h => h.Instance)
                .ThenInclude(i => i.Customer)
            .Where(h => h.CheckName == "xibo-version")
            .GroupBy(h => h.InstanceId)
            .Select(g => g.OrderByDescending(h => h.OccurredAt).First())
            .ToListAsync();

        // Latest OauthAppRegistry per instance
        var latestOauth = await db.OauthAppRegistries
            .AsNoTracking()
            .GroupBy(o => o.InstanceId)
            .Select(g => g.OrderByDescending(o => o.CreatedAt).First())
            .ToDictionaryAsync(o => o.InstanceId);

        var rows = versionEvents.Select(h =>
        {
            var parts = ParseVersionMessage(h.Message);
            latestOauth.TryGetValue(h.InstanceId, out var oauth);
            var credAgeDays = oauth is not null
                ? (int)(DateTime.UtcNow - oauth.CreatedAt).TotalDays
                : -1;

            return new VersionDriftCsvRow
            {
                Abbrev = h.Instance.Customer.Abbreviation,
                CurrentVersion = parts.Current,
                LatestVersion = parts.Latest,
                CredentialAgeDays = credAgeDays,
            };
        })
        .OrderBy(r => r.Abbrev)
        .ToList();

        _logger.LogInformation("Generated version drift CSV: {RowCount} rows", rows.Count);

        return WriteCsv(rows);
    }

    private static byte[] WriteCsv<T>(IEnumerable<T> records)
    {
        using var stream = new MemoryStream();
        using var writer = new StreamWriter(stream, Encoding.UTF8);
        using var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            HasHeaderRecord = true,
        });
        csv.WriteRecords(records);
        writer.Flush();
        return stream.ToArray();
    }

    /// <summary>
    /// Parses the health event message to extract current/latest version strings.
    /// Expected format from XiboVersionHealthCheck: "Current: X.Y.Z, Latest: A.B.C" or similar.
    /// Returns ("unknown", "unknown") if parsing fails.
    /// </summary>
    private static (string Current, string Latest) ParseVersionMessage(string? message)
    {
        if (string.IsNullOrWhiteSpace(message))
            return ("unknown", "unknown");

        var current = "unknown";
        var latest = "unknown";

        // Try to extract "Current: X.Y.Z" pattern
        var currentIdx = message.IndexOf("Current:", StringComparison.OrdinalIgnoreCase);
        if (currentIdx >= 0)
        {
            var afterCurrent = message[(currentIdx + 8)..].Trim();
            var end = afterCurrent.IndexOfAny([',', ' ', '\n']);
            current = end > 0 ? afterCurrent[..end].Trim() : afterCurrent.Trim();
        }

        var latestIdx = message.IndexOf("Latest:", StringComparison.OrdinalIgnoreCase);
        if (latestIdx >= 0)
        {
            var afterLatest = message[(latestIdx + 7)..].Trim();
            var end = afterLatest.IndexOfAny([',', ' ', '\n']);
            latest = end > 0 ? afterLatest[..end].Trim() : afterLatest.Trim();
        }

        return (current, latest);
    }
}

internal sealed class BillingCsvRow
{
    public string CustomerAbbrev { get; set; } = string.Empty;
    public string CompanyName { get; set; } = string.Empty;
    public DateOnly Date { get; set; }
    public int ScreenCount { get; set; }
    public string Plan { get; set; } = string.Empty;
}

internal sealed class VersionDriftCsvRow
{
    public string Abbrev { get; set; } = string.Empty;
    public string CurrentVersion { get; set; } = string.Empty;
    public string LatestVersion { get; set; } = string.Empty;
    public int CredentialAgeDays { get; set; }
}
