using System.Data.Common;
using System.Globalization;
using Eis.Infrastructure.Data;

namespace Eis.Infrastructure.Administration;

internal sealed record AdminCenterSnapshot(
    IReadOnlyList<AdminCenter> Centers,
    IReadOnlyList<AdminCenterRoom> Rooms,
    IReadOnlyList<AdminCenterChange> Changes,
    IReadOnlyList<AdminCenterChangeRoom> ChangeRooms);

internal sealed record AdminCenter(
    string Id,
    string SchoolId,
    string Code,
    string Name,
    string ProvinceCode,
    string ProvinceName,
    string CityCode,
    string CityName,
    string DistrictCode,
    string DistrictName,
    string Address,
    string Contact,
    string ManagerName,
    string ManagerPhone,
    string EmergencyPhone,
    string GateOpenTime,
    string Transport,
    string Status,
    string Notes,
    string Rooms,
    string UpdatedAt);

internal sealed record AdminCenterRoom(
    string Id,
    string CenterId,
    string Code,
    string Name,
    string Building,
    string Floor,
    int Capacity,
    string SeatPlan,
    int SeatStart,
    int SeatEnd,
    string RoomType,
    string Status,
    string Notes);

internal sealed record AdminCenterChange(
    string Id,
    string? CenterId,
    string SchoolId,
    string RequestType,
    string Code,
    string Name,
    string ProvinceCode,
    string ProvinceName,
    string CityCode,
    string CityName,
    string DistrictCode,
    string DistrictName,
    string Address,
    string Contact,
    string ManagerName,
    string ManagerPhone,
    string EmergencyPhone,
    string GateOpenTime,
    string Transport,
    string CenterStatus,
    string Notes,
    string Status,
    string ReviewNote,
    string? RequestedBy,
    string CreatedAt,
    string? ReviewedAt);

internal sealed record AdminCenterChangeRoom(
    string Id,
    string RequestId,
    string? RoomId,
    string Code,
    string Name,
    string Building,
    string Floor,
    int Capacity,
    string SeatPlan,
    int SeatStart,
    int SeatEnd,
    string RoomType,
    string Status,
    string Notes);

internal sealed class AdminCenterSnapshotLoader(IRelationalConnectionFactory connectionFactory)
{
    public async Task<AdminCenterSnapshot> LoadAsync(CancellationToken cancellationToken)
    {
        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        var centers = await QueryAsync(connection,
            """
            SELECT id, school_id, code, name, province_code, province_name, city_code, city_name,
              district_code, district_name, address, contact, manager_name, manager_phone, emergency_phone,
              gate_open_time, transport, status, notes, rooms, updated_at
            FROM test_centers ORDER BY school_id, name, id
            """,
            ReadCenter,
            cancellationToken);
        var rooms = await QueryAsync(connection,
            """
            SELECT id, center_id, code, name, building, floor, capacity, seat_plan, seat_start, seat_end,
              room_type, status, notes FROM test_rooms ORDER BY center_id, code, id
            """,
            ReadRoom,
            cancellationToken);
        var changes = await QueryAsync(connection,
            """
            SELECT id, center_id, school_id, request_type, code, name, province_code, province_name,
              city_code, city_name, district_code, district_name, address, contact, manager_name,
              manager_phone, emergency_phone, gate_open_time, transport, center_status, notes, status,
              review_note, requested_by, created_at, reviewed_at
            FROM center_change_requests ORDER BY created_at DESC, id
            """,
            ReadChange,
            cancellationToken);
        var changeRooms = await QueryAsync(connection,
            """
            SELECT id, request_id, room_id, code, name, building, floor, capacity, seat_plan, seat_start,
              seat_end, room_type, status, notes FROM center_change_rooms ORDER BY request_id, code, id
            """,
            ReadChangeRoom,
            cancellationToken);
        return new(centers, rooms, changes, changeRooms);
    }

    private static AdminCenter ReadCenter(DbDataReader reader) => new(
        Text(reader, "id"), Text(reader, "school_id"), Text(reader, "code"), Text(reader, "name"),
        Text(reader, "province_code"), Text(reader, "province_name"), Text(reader, "city_code"), Text(reader, "city_name"),
        Text(reader, "district_code"), Text(reader, "district_name"), Text(reader, "address"), Optional(reader, "contact") ?? "",
        Optional(reader, "manager_name") ?? "", Optional(reader, "manager_phone") ?? "", Optional(reader, "emergency_phone") ?? "",
        Optional(reader, "gate_open_time") ?? "", Optional(reader, "transport") ?? "", Text(reader, "status"),
        Optional(reader, "notes") ?? "", Text(reader, "rooms"), Text(reader, "updated_at"));

    private static AdminCenterRoom ReadRoom(DbDataReader reader) => new(
        Text(reader, "id"), Text(reader, "center_id"), Text(reader, "code"), Text(reader, "name"), Text(reader, "building"),
        Optional(reader, "floor") ?? "", Integer(reader, "capacity"), Optional(reader, "seat_plan") ?? "",
        Integer(reader, "seat_start"), Integer(reader, "seat_end"), Text(reader, "room_type"), Text(reader, "status"), Optional(reader, "notes") ?? "");

    private static AdminCenterChange ReadChange(DbDataReader reader) => new(
        Text(reader, "id"), Optional(reader, "center_id"), Text(reader, "school_id"), Text(reader, "request_type"),
        Text(reader, "code"), Text(reader, "name"), Text(reader, "province_code"), Text(reader, "province_name"),
        Text(reader, "city_code"), Text(reader, "city_name"), Text(reader, "district_code"), Text(reader, "district_name"),
        Text(reader, "address"), Optional(reader, "contact") ?? "", Optional(reader, "manager_name") ?? "",
        Optional(reader, "manager_phone") ?? "", Optional(reader, "emergency_phone") ?? "", Optional(reader, "gate_open_time") ?? "",
        Optional(reader, "transport") ?? "", Text(reader, "center_status"), Optional(reader, "notes") ?? "", Text(reader, "status"),
        Optional(reader, "review_note") ?? "", Optional(reader, "requested_by"), Text(reader, "created_at"), Optional(reader, "reviewed_at"));

    private static AdminCenterChangeRoom ReadChangeRoom(DbDataReader reader) => new(
        Text(reader, "id"), Text(reader, "request_id"), Optional(reader, "room_id"), Text(reader, "code"), Text(reader, "name"),
        Text(reader, "building"), Optional(reader, "floor") ?? "", Integer(reader, "capacity"), Optional(reader, "seat_plan") ?? "",
        Integer(reader, "seat_start"), Integer(reader, "seat_end"), Text(reader, "room_type"), Text(reader, "status"), Optional(reader, "notes") ?? "");

    private static async Task<IReadOnlyList<T>> QueryAsync<T>(
        DbConnection connection,
        string sql,
        Func<DbDataReader, T> map,
        CancellationToken cancellationToken)
    {
        await using var command = connection.CreateCommand();
        command.CommandText = sql;
        await using var reader = await command.ExecuteReaderAsync(cancellationToken);
        var output = new List<T>();
        while (await reader.ReadAsync(cancellationToken)) output.Add(map(reader));
        return output;
    }

    private static string Text(DbDataReader reader, string name) =>
        Convert.ToString(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture) ?? "";

    private static string? Optional(DbDataReader reader, string name)
    {
        var ordinal = reader.GetOrdinal(name);
        return reader.IsDBNull(ordinal) ? null : Convert.ToString(reader.GetValue(ordinal), CultureInfo.InvariantCulture);
    }

    private static int Integer(DbDataReader reader, string name) =>
        Convert.ToInt32(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture);
}
