using Xunit;
using Moq;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Hangfire;
using Hangfire.Common;
using QuantEngine.Web.Services;
using QuantEngine.Application.Services;

namespace QuantEngine.Core.Tests;

public class SchedulerServiceTests
{
    [Fact]
    public void InitializeSchedules_RegistersAllFourRequiredJobs()
    {
        // Arrange
        var loggerMock = new Mock<ILogger<SchedulerService>>();
        var jobClientMock = new Mock<IBackgroundJobClient>();
        var recurringJobManagerMock = new Mock<IRecurringJobManager>();
        var scopeFactoryMock = new Mock<IServiceScopeFactory>();

        var configMock = new Mock<IConfiguration>();
        configMock.Setup(c => c["Kis:AccountMode"]).Returns("mock");

        var parser = new GatherTradingDataParser();

        var service = new SchedulerService(
            loggerMock.Object,
            jobClientMock.Object,
            recurringJobManagerMock.Object,
            scopeFactoryMock.Object,
            configMock.Object,
            parser,
            Options.Create(new SchedulerServiceOptions())
        );

        // Act
        service.InitializeSchedules();

        // Assert
        // Verify daily-collection was added or updated
        recurringJobManagerMock.Verify(m => m.AddOrUpdate(
            "daily-collection",
            It.IsAny<Job>(),
            "0 9 * * *",
            It.IsAny<RecurringJobOptions>()
        ), Times.Once);

        // Verify hourly-price-update was added or updated
        recurringJobManagerMock.Verify(m => m.AddOrUpdate(
            "hourly-price-update",
            It.IsAny<Job>(),
            "0 9,11,13,15 * * 1-5",
            It.IsAny<RecurringJobOptions>()
        ), Times.Once);

        // Verify weekly-report was added or updated
        recurringJobManagerMock.Verify(m => m.AddOrUpdate(
            "weekly-report",
            It.IsAny<Job>(),
            "0 17 * * 5",
            It.IsAny<RecurringJobOptions>()
        ), Times.Once);

        // Verify monthly-optimization was added or updated
        recurringJobManagerMock.Verify(m => m.AddOrUpdate(
            "monthly-optimization",
            It.IsAny<Job>(),
            "0 2 1 * *",
            It.IsAny<RecurringJobOptions>()
        ), Times.Once);
    }

    [Fact]
    public void GetRecurringJobDefinitions_ReturnsCanonicalDefinitions()
    {
        var service = CreateService();

        var defs = service.GetRecurringJobDefinitions();

        Assert.Equal(4, defs.Count);
        Assert.Contains(defs, d => d.JobId == "daily-collection" && d.IsRecurring);
        Assert.Contains(defs, d => d.JobId == "hourly-price-update" && d.IsRecurring);
        Assert.Contains(defs, d => d.JobId == "weekly-report" && d.IsRecurring);
        Assert.Contains(defs, d => d.JobId == "monthly-optimization" && d.IsRecurring);
    }

    [Fact]
    public async Task FetchPriceAsync_WritesAuditTrail()
    {
        var root = FindRepoRoot();
        var auditDir = Path.Combine(root, "Temp", "scheduler_audit");
        if (Directory.Exists(auditDir))
        {
            Directory.Delete(auditDir, true);
        }

        var service = CreateService();
        await service.FetchPriceAsync("005930");

        var auditPath = Path.Combine(auditDir, "fetch-price.jsonl");
        Assert.True(File.Exists(auditPath));
        var lines = File.ReadAllLines(auditPath);
        Assert.NotEmpty(lines);
        Assert.Contains("\"State\":\"SUCCEEDED\"", lines[0]);
    }

    [Fact]
    public async Task GenerateWeeklyReportAsync_WritesWeeklyReportArtifact()
    {
        var root = FindRepoRoot();
        var reportDir = Path.Combine(root, "Temp", "scheduler_audit", "reports");
        if (Directory.Exists(reportDir))
        {
            Directory.Delete(reportDir, true);
        }

        var service = CreateService();
        await service.GenerateWeeklyReportAsync();

        var reportPath = Path.Combine(reportDir, $"weekly-report-{DateTime.UtcNow:yyyyMMdd}.json");
        Assert.True(File.Exists(reportPath));
        var text = await File.ReadAllTextAsync(reportPath);
        Assert.Contains("\"report_type\": \"weekly-report\"", text);
        Assert.Contains("\"ticker_universe\"", text);
    }

    [Fact]
    public async Task RunMonthlyOptimizationAsync_WritesOptimizationArtifact()
    {
        var root = FindRepoRoot();
        var reportDir = Path.Combine(root, "Temp", "scheduler_audit", "reports");
        if (Directory.Exists(reportDir))
        {
            Directory.Delete(reportDir, true);
        }

        var service = CreateService();
        await service.RunMonthlyOptimizationAsync();

        var reportPath = Path.Combine(reportDir, $"monthly-optimization-{DateTime.UtcNow:yyyyMMdd}.json");
        Assert.True(File.Exists(reportPath));
        var text = await File.ReadAllTextAsync(reportPath);
        Assert.Contains("\"report_type\": \"monthly-optimization\"", text);
        Assert.Contains("\"optimization_scope\"", text);
    }

    [Fact]
    public void LoadTickersFromJson_WhenFileMissing_FallsBackToDefaultUniverse()
    {
        var repoRoot = FindRepoRoot();
        var jsonPath = Path.Combine(repoRoot, "GatherTradingData.json");
        var backupPath = jsonPath + ".bak";

        if (File.Exists(jsonPath))
        {
            File.Copy(jsonPath, backupPath, true);
            File.Delete(jsonPath);
        }

        try
        {
            var service = CreateService();
            var tickers = InvokeLoadTickersFromJson(service);

            Assert.Single(tickers);
            Assert.Equal("005930", tickers[0]);
        }
        finally
        {
            if (File.Exists(backupPath))
            {
                File.Copy(backupPath, jsonPath, true);
                File.Delete(backupPath);
            }
        }
    }

    [Fact]
    public void LoadTickersFromJson_WhenFileExists_ReturnsDistinctTickers()
    {
        var repoRoot = FindRepoRoot();
        var jsonPath = Path.Combine(repoRoot, "GatherTradingData.json");
        var backupPath = jsonPath + ".bak";

        if (File.Exists(jsonPath))
        {
            File.Copy(jsonPath, backupPath, true);
        }

        try
        {
            File.WriteAllText(jsonPath, """
            {
              "data": {
                "data_feed": [
                  {"Ticker":"005930"},
                  {"Ticker":"000660"},
                  {"Ticker":"005930"}
                ]
              }
            }
            """);

            var service = CreateService();
            var tickers = InvokeLoadTickersFromJson(service);

            Assert.Equal(new[] { "005930", "000660" }, tickers);
        }
        finally
        {
            if (File.Exists(backupPath))
            {
                File.Copy(backupPath, jsonPath, true);
                File.Delete(backupPath);
            }
            else
            {
                File.Delete(jsonPath);
            }
        }
    }

    private static SchedulerService CreateService()
    {
        var loggerMock = new Mock<ILogger<SchedulerService>>();
        var jobClientMock = new Mock<IBackgroundJobClient>();
        var recurringJobManagerMock = new Mock<IRecurringJobManager>();
        var scopeFactoryMock = new Mock<IServiceScopeFactory>();
        var configMock = new Mock<IConfiguration>();
        configMock.Setup(c => c["Kis:AccountMode"]).Returns("mock");

        return new SchedulerService(
            loggerMock.Object,
            jobClientMock.Object,
            recurringJobManagerMock.Object,
            scopeFactoryMock.Object,
            configMock.Object,
            new GatherTradingDataParser(),
            Options.Create(new SchedulerServiceOptions())
        );
    }

    private static List<string> InvokeLoadTickersFromJson(SchedulerService service)
    {
        var method = typeof(SchedulerService).GetMethod("LoadTickersFromJson", BindingFlags.Instance | BindingFlags.NonPublic);
        Assert.NotNull(method);
        return (List<string>)method!.Invoke(service, null)!;
    }

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

        throw new InvalidOperationException("Repository root not found.");
    }
}
