using Jiaowu.Api.Controllers;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Tests;

public sealed class BaseDataControllerTests : IAsyncDisposable
{
    private readonly SqliteConnection connection = new("Data Source=:memory:");
    private readonly AppDbContext db;
    private readonly BaseDataController controller;

    public BaseDataControllerTests()
    {
        connection.Open();
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlite(connection)
            .Options;
        db = new AppDbContext(options);
        db.Database.EnsureCreated();
        controller = new BaseDataController(db, NoOpAppCache.Instance);
    }

    [Fact]
    public async Task SetCurrentTerm_clears_previous_current_in_one_save()
    {
        var previous = CreateTerm("2025-2026-2", true);
        var next = CreateTerm("2026-2027-1", false);
        db.AcademicTerms.AddRange(previous, next);
        await db.SaveChangesAsync();

        var result = await controller.SetCurrentTerm(next.Id, CancellationToken.None);

        Assert.Null(result.Result);
        db.ChangeTracker.Clear();
        Assert.False((await db.AcademicTerms.FindAsync(previous.Id))!.IsCurrent);
        Assert.True((await db.AcademicTerms.FindAsync(next.Id))!.IsCurrent);
        Assert.Equal(1, await db.AcademicTerms.CountAsync(x => x.IsCurrent));
    }

    [Fact]
    public async Task Archive_requires_switching_current_and_is_reversible()
    {
        var current = CreateTerm("2026-2027-1", true);
        var history = CreateTerm("2025-2026-2", false);
        db.AcademicTerms.AddRange(current, history);
        await db.SaveChangesAsync();

        var currentResult = await controller.ArchiveTerm(
            current.Id, CancellationToken.None);
        Assert.IsType<ConflictObjectResult>(currentResult.Result);

        var archiveResult = await controller.ArchiveTerm(
            history.Id, CancellationToken.None);
        Assert.Null(archiveResult.Result);
        Assert.True(history.IsArchived);
        Assert.NotNull(history.ArchivedAt);
        Assert.True(history.IsEnabled);

        var unarchiveResult = await controller.UnarchiveTerm(
            history.Id, CancellationToken.None);
        Assert.Null(unarchiveResult.Result);
        Assert.False(history.IsArchived);
        Assert.Null(history.ArchivedAt);
    }

    [Fact]
    public async Task Archived_term_must_be_unarchived_before_becoming_current()
    {
        var current = CreateTerm("2026-2027-1", true);
        var archived = CreateTerm("2025-2026-2", false);
        archived.IsArchived = true;
        archived.ArchivedAt = DateTime.UtcNow;
        db.AcademicTerms.AddRange(current, archived);
        await db.SaveChangesAsync();

        var result = await controller.SetCurrentTerm(
            archived.Id, CancellationToken.None);

        Assert.IsType<ConflictObjectResult>(result.Result);
        Assert.True(current.IsCurrent);
        Assert.False(archived.IsCurrent);
    }

    [Fact]
    public async Task Current_term_cannot_be_cleared_without_a_replacement()
    {
        var current = CreateTerm("2026-2027-1", true);
        db.AcademicTerms.Add(current);
        await db.SaveChangesAsync();

        var request = new TermRequest(
            current.Code,
            current.Name,
            true,
            current.AcademicYear,
            current.Season,
            current.StartDate,
            current.EndDate,
            false);
        var result = await controller.UpdateTerm(
            current.Id, request, CancellationToken.None);

        Assert.IsType<ConflictObjectResult>(result.Result);
        Assert.True(current.IsCurrent);
    }

    private static AcademicTerm CreateTerm(string code, bool isCurrent) =>
        new()
        {
            Code = code,
            Name = code,
            AcademicYear = code[..9],
            Season = TermSeason.Autumn,
            StartDate = new DateOnly(2026, 9, 1),
            EndDate = new DateOnly(2027, 1, 20),
            IsCurrent = isCurrent,
            IsEnabled = true
        };

    public async ValueTask DisposeAsync()
    {
        await db.DisposeAsync();
        await connection.DisposeAsync();
    }
}
