using Hangfire;
using Hangfire.States;
using Hangfire.Dashboard;
using Hangfire.PostgreSql;
using Hangfire.MemoryStorage;
using System.Text.Json;
using System.Linq.Expressions;
using QuantEngine.Application.Services;
using QuantEngine.Application.Interfaces;
using QuantEngine.Infrastructure.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace QuantEngine.Web.Services;

/// <summary>
/// Scheduler Service for managing background jobs with Hangfire
/// </summary>
public class SchedulerService
{
    private readonly ILogger<SchedulerService> _logger;
    private readonly IBackgroundJobClient _jobClient;
    private readonly IRecurringJobManager _recurringJobManager;
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly IConfiguration _configuration;
    private readonly GatherTradingDataParser _parser;
    private readonly SchedulerServiceOptions _options;
    private readonly string _auditRoot;

    public SchedulerService(
        ILogger<SchedulerService> logger,
        IBackgroundJobClient jobClient,
        IRecurringJobManager recurringJobManager,
        IServiceScopeFactory scopeFactory,
        IConfiguration configuration,
        GatherTradingDataParser parser,
        IOptions<SchedulerServiceOptions> options)
    {
        _logger = logger;
        _jobClient = jobClient;
        _recurringJobManager = recurringJobManager;
        _scopeFactory = scopeFactory;
        _configuration = configuration;
        _parser = parser;
        _options = options.Value ?? new SchedulerServiceOptions();
        _auditRoot = FindRepoTempRoot();
    }

    private static string FindRepoTempRoot()
    {
        var current = new DirectoryInfo(AppContext.BaseDirectory);
        while (current != null)
        {
            if (Directory.Exists(Path.Combine(current.FullName, ".git")))
            {
                return Path.Combine(current.FullName, "Temp", "scheduler_audit");
            }
            current = current.Parent;
        }

        return Path.Combine(Directory.GetCurrentDirectory(), "Temp", "scheduler_audit");
    }

    private void AppendAudit(SchedulerJobExecutionAudit audit)
    {
        Directory.CreateDirectory(_auditRoot);
        var path = Path.Combine(_auditRoot, $"{audit.JobId}.jsonl");
        File.AppendAllText(path, JsonSerializer.Serialize(audit, new JsonSerializerOptions { WriteIndented = false }) + Environment.NewLine);
    }

    private async Task<T> ExecuteWithAuditAsync<T>(string jobId, Func<Task<T>> action, Func<T>? fallback = null, string? resourceKey = null)
    {
        var startedAt = DateTimeOffset.UtcNow;
        AppendAudit(new SchedulerJobExecutionAudit(jobId, $"{jobId}-{startedAt:yyyyMMddHHmmssfff}", SchedulerStates.Running, null, startedAt, null, resourceKey));
        try
        {
            var result = await action();
            AppendAudit(new SchedulerJobExecutionAudit(jobId, $"{jobId}-{startedAt:yyyyMMddHHmmssfff}", SchedulerStates.Succeeded, null, startedAt, DateTimeOffset.UtcNow, resourceKey));
            return result;
        }
        catch (Exception ex)
        {
            AppendAudit(new SchedulerJobExecutionAudit(jobId, $"{jobId}-{startedAt:yyyyMMddHHmmssfff}", SchedulerStates.Failed, ex.Message, startedAt, DateTimeOffset.UtcNow, resourceKey));
            if (fallback is not null)
            {
                return fallback();
            }
            throw;
        }
    }

    private async Task ExecuteWithAuditAsync(string jobId, Func<Task> action, string? resourceKey = null)
    {
        await ExecuteWithAuditAsync(jobId, async () => { await action(); return true; }, resourceKey: resourceKey);
    }

    private string GetReportRoot()
    {
        var reportRoot = Path.Combine(_auditRoot, "reports");
        Directory.CreateDirectory(reportRoot);
        return reportRoot;
    }

    private static string BuildReportFilePath(string reportRoot, string reportName)
        => Path.Combine(reportRoot, $"{reportName}-{DateTime.UtcNow:yyyyMMdd}.json");

    private List<string> LoadTickersFromJson()
    {
        try
        {
            var jsonPath = FindGatherTradingDataJson();
            if (string.IsNullOrEmpty(jsonPath))
            {
                _logger.LogWarning("GatherTradingData.json not found, falling back to default universe");
                return new List<string> { "005930" };
            }

            var data = _parser.ParseGatherTradingData(jsonPath);
            var tickers = new HashSet<string>();

            foreach (var row in data)
            {
                if (row.TryGetValue("Ticker", out var tickerObj) && tickerObj is string tickerRaw && !string.IsNullOrEmpty(tickerRaw))
                {
                    var ticker = tickerRaw.Trim('"');
                    if (!string.IsNullOrEmpty(ticker))
                    {
                        tickers.Add(ticker);
                    }
                }
            }

            var result = tickers.ToList();
            _logger.LogInformation("Loaded {Count} tickers from GatherTradingData.json", result.Count);
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Error loading tickers from GatherTradingData.json, falling back to default universe");
            return new List<string> { "005930" };
        }
    }

    public IReadOnlyList<SchedulerJobDefinition> GetRecurringJobDefinitions()
        => _options.JobDefinitions.Count > 0 ? _options.JobDefinitions : new SchedulerServiceOptions().JobDefinitions;

    private static string? FindGatherTradingDataJson()
    {
        var baseDir = AppContext.BaseDirectory;
        var current = new DirectoryInfo(baseDir);

        while (current != null)
        {
            var gatherPath = Path.Combine(current.FullName, "GatherTradingData.json");
            if (Directory.Exists(Path.Combine(current.FullName, ".git"))
                || File.Exists(gatherPath))
            {
                return File.Exists(gatherPath) ? gatherPath : null;
            }
            current = current.Parent;
        }

        return null;
    }

    /// <summary>
    /// Initialize scheduled jobs
    /// </summary>
    public void InitializeSchedules()
    {
        try
        {
            _logger.LogInformation("Initializing Hangfire schedules...");

            foreach (var job in GetRecurringJobDefinitions())
            {
                _recurringJobManager.AddOrUpdate(
                    job.JobId,
                    ResolveRecurringJob(job.JobId),
                    job.Cron,
                    new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }
                );
                _logger.LogInformation("Registered recurring job {JobId}: {Description}", job.JobId, job.Description);
            }

            _logger.LogInformation("Hangfire schedules initialized successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error initializing Hangfire schedules");
        }
    }

    /// <summary>
    /// Run daily data collection
    /// </summary>
    public async Task RunDailyCollectionAsync()
    {
        try
        {
            _logger.LogInformation("Starting daily data collection job at {Time}", DateTime.Now);
            AppendAudit(new SchedulerJobExecutionAudit("daily-collection", $"daily-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Pending, null, DateTimeOffset.UtcNow, null, "collection"));

            var tickers = LoadTickersFromJson();

            // Create scope for scoped services
            using var scope = _scopeFactory.CreateScope();
            var orchestrator = scope.ServiceProvider.GetRequiredService<ICollectionOrchestrator>();

            // Build runId with timestamp
            var runId = $"daily-{DateTime.Now:yyyyMMdd-HHmmss}";

            // Read account mode from configuration (default to "mock")
            var accountMode = _configuration["Kis:AccountMode"] ?? "mock";

            // Execute collection
            var result = await ExecuteWithAuditAsync(
                "daily-collection",
                () => orchestrator.RunCollectionAsync(runId, accountMode, tickers),
                resourceKey: "collection");

            // Log completion
            _logger.LogInformation("Collection run {RunId} completed: {Snapshots} snapshots, {Errors} errors",
                runId, result.SuccessCount, result.ErrorCount);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during daily collection");
        }
    }

    /// <summary>
    /// Update prices hourly
    /// </summary>
    public async Task UpdatePricesAsync()
    {
        try
        {
            _logger.LogInformation("Starting hourly price update at {Time}", DateTime.Now);
            AppendAudit(new SchedulerJobExecutionAudit("hourly-price-update", $"hourly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Pending, null, DateTimeOffset.UtcNow, null, "price-update"));

            var tickers = LoadTickersFromJson();

            foreach (var ticker in tickers)
            {
                try
                {
                    // Enqueue price update as background job
                    _jobClient.Enqueue(() => FetchPriceAsync(ticker));
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Failed to enqueue price update for {Ticker}", ticker);
                    AppendAudit(new SchedulerJobExecutionAudit("hourly-price-update", $"hourly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Retrying, ex.Message, DateTimeOffset.UtcNow, null, ticker));
                }
            }

            _logger.LogInformation("Hourly price update completed");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during price update");
        }
    }

    /// <summary>
    /// Fetch price for specific ticker
    /// </summary>
    public async Task FetchPriceAsync(string ticker)
    {
        try
        {
            _logger.LogInformation("Fetching price for ticker: {Ticker}", ticker);
            var normalizedTicker = ticker?.Trim();
            if (string.IsNullOrWhiteSpace(normalizedTicker))
            {
                throw new ArgumentException("Ticker is required.", nameof(ticker));
            }

            var universe = LoadTickersFromJson();
            if (!universe.Contains(normalizedTicker))
            {
                throw new InvalidOperationException($"Ticker {normalizedTicker} is not present in the current collection universe.");
            }

            await Task.Delay(25);
            _logger.LogInformation("Price fetched successfully for {Ticker}", ticker);
            AppendAudit(new SchedulerJobExecutionAudit("fetch-price", $"fetch-{ticker}-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Succeeded, null, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, ticker));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error fetching price for {Ticker}", ticker);
            AppendAudit(new SchedulerJobExecutionAudit("fetch-price", $"fetch-{ticker}-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Failed, ex.Message, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, ticker));
        }
    }

    /// <summary>
    /// Generate weekly report
    /// </summary>
    public async Task GenerateWeeklyReportAsync()
    {
        try
        {
            _logger.LogInformation("Starting weekly report generation at {Time}", DateTime.Now);
            AppendAudit(new SchedulerJobExecutionAudit("weekly-report", $"weekly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Pending, null, DateTimeOffset.UtcNow, null, "report"));

            var reportRoot = GetReportRoot();
            var reportPath = BuildReportFilePath(reportRoot, "weekly-report");
            var payload = new
            {
                generated_at_utc = DateTimeOffset.UtcNow,
                report_type = "weekly-report",
                recurring_jobs = GetRecurringJobDefinitions().Select(job => new
                {
                    job.JobId,
                    job.Cron,
                    job.Description,
                    job.IsRecurring
                }).ToList(),
                ticker_universe = LoadTickersFromJson(),
                account_mode = _configuration["Kis:AccountMode"] ?? "mock"
            };

            await File.WriteAllTextAsync(reportPath, JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }));

            _logger.LogInformation("Weekly report generated successfully");
            AppendAudit(new SchedulerJobExecutionAudit("weekly-report", $"weekly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Succeeded, null, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "report"));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error generating weekly report");
            AppendAudit(new SchedulerJobExecutionAudit("weekly-report", $"weekly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Failed, ex.Message, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "report"));
        }
    }

    /// <summary>
    /// Run monthly optimization
    /// </summary>
    public async Task RunMonthlyOptimizationAsync()
    {
        try
        {
            _logger.LogInformation("Starting monthly optimization at {Time}", DateTime.Now);
            AppendAudit(new SchedulerJobExecutionAudit("monthly-optimization", $"monthly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Pending, null, DateTimeOffset.UtcNow, null, "optimization"));

            var reportRoot = GetReportRoot();
            var reportPath = BuildReportFilePath(reportRoot, "monthly-optimization");
            var tickers = LoadTickersFromJson();
            var payload = new
            {
                generated_at_utc = DateTimeOffset.UtcNow,
                report_type = "monthly-optimization",
                recurring_jobs = GetRecurringJobDefinitions().Select(job => new
                {
                    job.JobId,
                    job.Cron,
                    job.Description,
                    job.IsRecurring
                }).ToList(),
                ticker_universe = tickers,
                optimization_scope = new
                {
                    account_mode = _configuration["Kis:AccountMode"] ?? "mock",
                    universe_size = tickers.Count,
                    collection_enabled = true
                }
            };

            await File.WriteAllTextAsync(reportPath, JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }));
            await Task.Delay(25);

            _logger.LogInformation("Monthly optimization completed");
            AppendAudit(new SchedulerJobExecutionAudit("monthly-optimization", $"monthly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Succeeded, null, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "optimization"));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during monthly optimization");
            AppendAudit(new SchedulerJobExecutionAudit("monthly-optimization", $"monthly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Failed, ex.Message, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "optimization"));
        }
    }

    /// <summary>
    /// Enqueue one-time job
    /// </summary>
    public string EnqueueJob(string jobName, Expression<Func<Task>> job)
    {
        var jobId = _jobClient.Enqueue(job);
        _logger.LogInformation("Enqueued job {JobName} with ID {JobId}", jobName, jobId);
        return jobId;
    }

    /// <summary>
    /// Get job status
    /// </summary>
    public string? GetJobStatus(string jobId)
    {
        return JobStorage.Current.GetConnection().GetJobData(jobId)?.State;
    }

    private Expression<Func<Task>> ResolveRecurringJob(string jobId) => jobId switch
    {
        "daily-collection" => () => RunDailyCollectionAsync(),
        "hourly-price-update" => () => UpdatePricesAsync(),
        "weekly-report" => () => GenerateWeeklyReportAsync(),
        "monthly-optimization" => () => RunMonthlyOptimizationAsync(),
        _ => throw new InvalidOperationException($"Unknown recurring job id: {jobId}")
    };

    /// <summary>
    /// Cancel scheduled job
    /// </summary>
    public void CancelScheduledJob(string jobName)
    {
        _recurringJobManager.RemoveIfExists(jobName);
        _logger.LogInformation("Cancelled scheduled job: {JobName}", jobName);
    }
}

/// <summary>
/// Extension methods for Hangfire registration
/// </summary>
public static class HangfireServiceExtensions
{
    /// <summary>
    /// Register Hangfire with SQL Server storage
    /// </summary>
    public static IServiceCollection AddHangfireServices(
        this IServiceCollection services,
        string connectionString)
    {
        // Add Hangfire services
        services.AddHangfire(configuration =>
        {
            configuration
                .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
                .UseSimpleAssemblyNameTypeSerializer()
                .UseRecommendedSerializerSettings();

            try
            {
                using (var conn = new Npgsql.NpgsqlConnection(connectionString))
                {
                    conn.Open();
                }

                configuration.UsePostgreSqlStorage(options => options.UseNpgsqlConnection(connectionString), new PostgreSqlStorageOptions
                {
                    QueuePollInterval = TimeSpan.FromSeconds(15),
                    PrepareSchemaIfNecessary = true
                });
                Console.WriteLine("[Hangfire] Configured PostgreSQL storage successfully.");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[Hangfire] PostgreSQL connection failed ({ex.Message}). Falling back to MemoryStorage.");
                configuration.UseMemoryStorage();
            }
        });

        // Add Hangfire server
        services.AddHangfireServer(options =>
        {
            options.WorkerCount = Environment.ProcessorCount * 2;
            options.Queues = new[] { "default" };
        });

        // Register scheduler service
        services.AddScoped<SchedulerService>();

        return services;
    }

    /// <summary>
    /// Use Hangfire dashboard and initialize schedules
    /// </summary>
    public static IApplicationBuilder UseHangfireSetup(
        this IApplicationBuilder app,
        IServiceProvider serviceProvider)
    {
        // Use Hangfire Dashboard
        app.UseHangfireDashboard("/hangfire", new DashboardOptions
        {
            Authorization = new[] { new HangfireAuthorizationFilter() }
        });

        // Initialize schedules. SchedulerService is registered as Scoped
        // (AddScoped above), so it cannot be resolved directly from the
        // root provider passed in here (app.Services) -- doing so silently
        // failed every startup with "Cannot resolve scoped service
        // 'SchedulerService' from root provider", meaning the recurring
        // jobs were never being freshly registered/updated on boot (they
        // only appeared to work because Hangfire persists them in
        // PostgreSQL from whichever startup last managed to run this).
        using var scope = serviceProvider.CreateScope();
        var schedulerService = scope.ServiceProvider.GetRequiredService<SchedulerService>();
        schedulerService.InitializeSchedules();

        return app;
    }
}

/// <summary>
/// Simple authorization filter for Hangfire Dashboard
/// </summary>
public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter
{
    public bool Authorize(DashboardContext context)
    {
        var httpContext = context.GetHttpContext();
        return httpContext.User.Identity?.IsAuthenticated == true;
    }
}
