using System.Reflection;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Controllers;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Domain.System;
using Jiaowu.Api.Infrastructure.BackgroundJobs;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Operations;
using Jiaowu.Api.Infrastructure.Observability;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Jiaowu.Api.Tests;

public sealed class OperationsControllerTests
{
    [Fact]
    public void Controller_is_restricted_to_super_admin()
    {
        var authorize = typeof(OperationsController)
            .GetCustomAttribute<AuthorizeAttribute>();

        Assert.NotNull(authorize);
        Assert.Equal(SystemRoles.SuperAdmin, authorize.Roles);
    }

    [Fact]
    public async Task Swagger_documentation_is_closed_by_default_and_can_be_enabled()
    {
        var root = CreateTemporaryRoot();
        try
        {
            await using var fixture = await OperationsFixture.CreateAsync(root);

            var initial = await fixture.Controller.GetSwaggerSettings(
                CancellationToken.None);
            var initialSettings = Assert.IsType<SwaggerDocumentationSettings>(
                Assert.IsType<OkObjectResult>(initial.Result).Value);
            Assert.False(initialSettings.IsEnabled);

            var updated = await fixture.Controller.UpdateSwaggerSettings(
                new UpdateSwaggerDocumentationSettings(true),
                CancellationToken.None);
            var updatedSettings = Assert.IsType<SwaggerDocumentationSettings>(
                Assert.IsType<OkObjectResult>(updated.Result).Value);
            Assert.True(updatedSettings.IsEnabled);

            var persisted = await fixture.Controller.GetSwaggerSettings(
                CancellationToken.None);
            var persistedSettings = Assert.IsType<SwaggerDocumentationSettings>(
                Assert.IsType<OkObjectResult>(persisted.Result).Value);
            Assert.True(persistedSettings.IsEnabled);
        }
        finally
        {
            DeleteTemporaryRoot(root);
        }
    }

    [Fact]
    public async Task Audit_and_failed_job_queries_return_operational_records()
    {
        var root = CreateTemporaryRoot();
        try
        {
            await using var fixture = await OperationsFixture.CreateAsync(root);
            var term = new AcademicTerm
            {
                Code = "OPS-TERM",
                Name = "运维测试学期",
                AcademicYear = "2026-2027",
                Season = TermSeason.Autumn,
                StartDate = new DateOnly(2026, 9, 1),
                EndDate = new DateOnly(2027, 1, 15)
            };
            var plan = new SchedulePlan
            {
                AcademicTerm = term,
                Name = "恢复演练排课方案",
                Version = "V1"
            };
            var job = new AutomaticScheduleJob
            {
                SchedulePlan = plan,
                Status = AutomaticScheduleJobStatus.Failed,
                ErrorMessage = "排课求解器未找到可行解。",
                CompletedAt = DateTime.UtcNow
            };
            var outbox = BackgroundJobOutboxMessage.Create(
                BackgroundJobKind.AutomaticSchedule,
                job.Id);
            outbox.State = BackgroundJobOutboxState.Completed;
            outbox.ProcessingAttempts = 3;
            fixture.Db.AuditLogs.Add(new AuditLog
            {
                UserName = "root",
                Method = "POST",
                Path = "/api/schedules",
                StatusCode = 500,
                IpAddress = "127.0.0.1"
            });
            fixture.Db.AddRange(term, plan, job, outbox);
            await fixture.Db.SaveChangesAsync();

            var auditAction = await fixture.Controller.GetAuditLogs(
                cancellationToken: CancellationToken.None);
            var auditResult = Assert.IsType<OkObjectResult>(auditAction.Result);
            var auditPage = Assert.IsType<PagedResult<AuditLogItem>>(auditResult.Value);
            Assert.Single(auditPage.Items);
            Assert.Equal(500, auditPage.Items.Single().StatusCode);

            var jobsAction = await fixture.Controller.GetFailedJobs(
                cancellationToken: CancellationToken.None);
            var jobsResult = Assert.IsType<OkObjectResult>(jobsAction.Result);
            var jobsPage =
                Assert.IsType<PagedResult<FailedBackgroundJobItem>>(jobsResult.Value);
            var failed = Assert.Single(jobsPage.Items);
            Assert.Equal("AutomaticSchedule", failed.Kind);
            Assert.Equal(3, failed.ProcessingAttempts);
            Assert.Contains("可行解", failed.ErrorMessage);
        }
        finally
        {
            DeleteTemporaryRoot(root);
        }
    }

    [Fact]
    public async Task Sqlite_backup_can_be_verified_in_an_isolated_restore_drill()
    {
        var root = CreateTemporaryRoot();
        try
        {
            await using var fixture = await OperationsFixture.CreateAsync(root);
            fixture.Db.AuditLogs.Add(new AuditLog
            {
                UserName = "backup-test",
                Method = "POST",
                Path = "/api/test",
                StatusCode = 204
            });
            await fixture.Db.SaveChangesAsync();

            var artifact = await fixture.Backups.CreateAsync(
                "自动化测试",
                CancellationToken.None);
            var result = await fixture.Backups.RunRestoreDrillAsync(
                artifact.Id,
                CancellationToken.None);
            var listed = await fixture.Backups.ListAsync(CancellationToken.None);

            Assert.True(File.Exists(Path.Combine(
                root,
                "backups",
                artifact.FileName)));
            Assert.Equal(64, artifact.Sha256.Length);
            Assert.True(result.Succeeded, result.Detail);
            Assert.True(result.TableCount > 0);
            Assert.True(Assert.Single(listed).LastDrillSucceeded);
        }
        finally
        {
            DeleteTemporaryRoot(root);
        }
    }

    private static string CreateTemporaryRoot()
    {
        var root = Path.Combine(
            Path.GetTempPath(),
            $"jiaowu-operations-{Guid.NewGuid():N}");
        Directory.CreateDirectory(root);
        return root;
    }

    private static void DeleteTemporaryRoot(string root)
    {
        var resolved = Path.GetFullPath(root);
        var temporary = Path.GetFullPath(Path.GetTempPath());
        Assert.StartsWith(temporary, resolved, StringComparison.OrdinalIgnoreCase);
        if (Directory.Exists(resolved))
            Directory.Delete(resolved, recursive: true);
    }

    private sealed class OperationsFixture : IAsyncDisposable
    {
        private readonly ServiceProvider provider;

        private OperationsFixture(
            ServiceProvider provider,
            AppDbContext db,
            OperationsController controller,
            DatabaseBackupService backups)
        {
            this.provider = provider;
            Db = db;
            Controller = controller;
            Backups = backups;
        }

        public AppDbContext Db { get; }
        public OperationsController Controller { get; }
        public DatabaseBackupService Backups { get; }

        public static async Task<OperationsFixture> CreateAsync(string root)
        {
            var databasePath = Path.Combine(root, "source.sqlite");
            var configuration = new ConfigurationBuilder()
                .AddInMemoryCollection(new Dictionary<string, string?>
                {
                    ["ConnectionStrings:SQLite"] =
                        $"Data Source={databasePath};Pooling=False"
                })
                .Build();
            var databaseOptions = new DatabaseOptions { Provider = "SQLite" };
            var cacheOptions = new AppCacheOptions();
            var operationsOptions = new OperationsOptions
            {
                BackupDirectory = "backups"
            };
            var dbContextOptions = new DbContextOptionsBuilder<AppDbContext>()
                .UseSqlite(configuration.GetConnectionString("SQLite"))
                .Options;
            var db = new AppDbContext(dbContextOptions);
            await db.Database.EnsureCreatedAsync();

            var services = new ServiceCollection()
                .AddLogging()
                .AddMemoryCache()
                .BuildServiceProvider();
            var logger = services.GetRequiredService<
                ILogger<DatabaseBackupService>>();
            var environment = new TestHostEnvironment
            {
                ContentRootPath = root
            };
            var transport = new InMemoryBackgroundJobTransport();
            var monitoring = new BackgroundJobMonitoringService(db);
            var health = new OperationalHealthService(
                db,
                services,
                transport,
                monitoring,
                databaseOptions,
                cacheOptions,
                configuration);
            var backups = new DatabaseBackupService(
                databaseOptions,
                operationsOptions,
                configuration,
                environment,
                logger);
            var performance = new PerformanceReportService(
                new HttpClient(),
                services.GetRequiredService<
                    Microsoft.Extensions.Caching.Memory.IMemoryCache>(),
                new PerformanceReportingOptions(),
                new ObservabilityOptions(),
                services.GetRequiredService<
                    ILogger<PerformanceReportService>>());
            var controller = new OperationsController(
                db,
                health,
                backups,
                performance,
                operationsOptions);
            return new OperationsFixture(services, db, controller, backups);
        }

        public async ValueTask DisposeAsync()
        {
            await Db.DisposeAsync();
            await provider.DisposeAsync();
        }
    }

    private sealed class TestHostEnvironment : IWebHostEnvironment
    {
        public string ApplicationName { get; set; } = "Jiaowu.Api.Tests";
        public IFileProvider WebRootFileProvider { get; set; } = new NullFileProvider();
        public string WebRootPath { get; set; } = "";
        public string EnvironmentName { get; set; } = Environments.Development;
        public string ContentRootPath { get; set; } = "";
        public IFileProvider ContentRootFileProvider { get; set; } =
            new NullFileProvider();
    }
}
