using Health.Application.Admin;
using Health.Domain.Entities;
using Health.Infrastructure.Admin;
using Health.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace Health.Tests;

public sealed class AdminServiceTests
{
    private static AppDbContext CreateDbContext()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        return new AppDbContext(options);
    }

    [Fact]
    public async Task AddDoctor_CreatesDoctorLoginAndProfileTogether()
    {
        await using var db = CreateDbContext();
        var service = new AdminService(db);

        var result = await service.AddDoctorAsync(
            new AddDoctorCommand("13800138001", "王医生", "主任医师", "心内科", "高血压"),
            CancellationToken.None);

        Assert.Equal(0, result.Code);
        var doctor = await db.Doctors.SingleAsync();
        var user = await db.Users.SingleAsync();
        var profile = await db.DoctorProfiles.SingleAsync();
        Assert.Equal("Doctor", user.Role);
        Assert.Equal(doctor.Id, profile.DoctorId);
        Assert.Equal(user.Id, profile.UserId);
        Assert.Equal(doctor.Name, user.Name);
        Assert.Equal(doctor.Name, profile.Name);
        Assert.Equal(doctor.Title, profile.Title);
        Assert.Equal(doctor.Department, profile.Department);
    }

    [Fact]
    public async Task AddDoctor_RejectsAPhoneAlreadyUsedByAnyAccount()
    {
        await using var db = CreateDbContext();
        db.Users.Add(new User
        {
            Id = Guid.NewGuid(),
            Phone = "13800138002",
            Role = "User",
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow,
        });
        await db.SaveChangesAsync();
        var service = new AdminService(db);

        var result = await service.AddDoctorAsync(
            new AddDoctorCommand("13800138002", "李医生", null, null, null),
            CancellationToken.None);

        Assert.NotEqual(0, result.Code);
        Assert.Empty(db.Doctors);
        Assert.Empty(db.DoctorProfiles);
    }

    [Fact]
    public async Task UpdateDoctor_SynchronizesDoctorLoginAndProfile()
    {
        await using var db = CreateDbContext();
        var (doctor, user, profile) = await SeedDoctorAsync(db);
        var service = new AdminService(db);

        var result = await service.UpdateDoctorAsync(
            doctor.Id,
            new UpdateDoctorCommand("13800138999", "新姓名", "副主任医师", "神经内科", "脑血管"),
            CancellationToken.None);

        Assert.Equal(0, result.Code);
        Assert.Equal("13800138999", doctor.Phone);
        Assert.Equal("13800138999", user.Phone);
        Assert.Equal("新姓名", doctor.Name);
        Assert.Equal("新姓名", user.Name);
        Assert.Equal("新姓名", profile.Name);
        Assert.Equal("副主任医师", profile.Title);
        Assert.Equal("神经内科", profile.Department);
    }

    [Fact]
    public async Task ToggleDoctor_SynchronizesAvailabilityButKeepsLoginEnabled()
    {
        await using var db = CreateDbContext();
        var (doctor, user, profile) = await SeedDoctorAsync(db);
        var service = new AdminService(db);

        var result = await service.ToggleDoctorAsync(doctor.Id, CancellationToken.None);

        Assert.Equal(0, result.Code);
        Assert.False(doctor.IsActive);
        Assert.False(profile.IsActive);
        Assert.Equal("Doctor", user.Role);
        Assert.NotNull(await db.Users.FindAsync(user.Id));
    }

    [Fact]
    public async Task DeleteDoctor_IsRejectedWhilePatientsAreBound()
    {
        await using var db = CreateDbContext();
        var (doctor, user, profile) = await SeedDoctorAsync(db);
        db.Users.Add(new User
        {
            Id = Guid.NewGuid(),
            Phone = "13800138088",
            Name = "患者",
            Role = "User",
            DoctorId = doctor.Id,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow,
        });
        await db.SaveChangesAsync();
        var service = new AdminService(db);

        var result = await service.DeleteDoctorAsync(doctor.Id, CancellationToken.None);

        Assert.NotEqual(0, result.Code);
        Assert.NotNull(await db.Doctors.FindAsync(doctor.Id));
        Assert.NotNull(await db.Users.FindAsync(user.Id));
        Assert.NotNull(await db.DoctorProfiles.FindAsync(profile.Id));
    }

    [Fact]
    public async Task DeleteUnusedDoctor_RemovesLoginProfileAndRefreshTokens()
    {
        await using var db = CreateDbContext();
        var (doctor, user, _) = await SeedDoctorAsync(db);
        db.RefreshTokens.Add(new RefreshToken
        {
            Id = Guid.NewGuid(),
            UserId = user.Id,
            Token = "refresh-token",
            ExpiresAt = DateTime.UtcNow.AddDays(1),
        });
        await db.SaveChangesAsync();
        var service = new AdminService(db);

        var result = await service.DeleteDoctorAsync(doctor.Id, CancellationToken.None);

        Assert.Equal(0, result.Code);
        Assert.Empty(db.Doctors);
        Assert.Empty(db.DoctorProfiles);
        Assert.Empty(db.Users);
        Assert.Empty(db.RefreshTokens);
    }

    private static async Task<(Doctor Doctor, User User, DoctorProfile Profile)> SeedDoctorAsync(AppDbContext db)
    {
        var doctor = new Doctor
        {
            Id = Guid.NewGuid(),
            Phone = "13800138003",
            Name = "原姓名",
            Title = "医师",
            Department = "内科",
            IsActive = true,
            CreatedAt = DateTime.UtcNow,
        };
        var user = new User
        {
            Id = Guid.NewGuid(),
            Phone = doctor.Phone,
            Name = doctor.Name,
            Role = "Doctor",
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow,
        };
        var profile = new DoctorProfile
        {
            Id = Guid.NewGuid(),
            DoctorId = doctor.Id,
            UserId = user.Id,
            Name = doctor.Name,
            Title = doctor.Title,
            Department = doctor.Department,
            IsActive = true,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow,
        };
        db.AddRange(doctor, user, profile);
        await db.SaveChangesAsync();
        return (doctor, user, profile);
    }
}
