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

namespace Eis.Infrastructure.Administration;

internal sealed record AdminAccountBatchSnapshot(
    IReadOnlyList<AdminSchool> Schools,
    IReadOnlyList<AdminClass> Classes,
    IReadOnlyList<AdminUser> Users,
    IReadOnlyList<AccountBatch> Batches,
    IReadOnlyList<AccountBatchItem> Items,
    IReadOnlyList<AccountWorkflow> Workflows,
    IReadOnlyList<AccountWorkflowInstance> Instances,
    IReadOnlyList<AccountWorkflowAction> Actions,
    IReadOnlyList<AccountNumberRule> NumberRules,
    string PreviewGender,
    string? PreviewSchoolId);

internal sealed record AccountBatch(string Id, string SchoolId, string? RequestedBy, string Status, string ReviewNote, string CreatedAt, string? ReviewedAt);
internal sealed record AccountBatchItem(string Id, string BatchId, string ClassId, int Position, string CandidateNumber, string InitialPassword, string? UserId, string? CreatedAt);
internal sealed record AccountWorkflow(string Id, string BusinessType, string Name, bool Active, string? UpdatedBy, string UpdatedAt, IReadOnlyList<AccountWorkflowStep> Steps);
internal sealed record AccountWorkflowStep(string Id, string Name, string AdminLevel, int Position);
internal sealed record AccountWorkflowInstance(string Id, string WorkflowId, string BusinessType, string BusinessId, string Status, int CurrentStep, string? AssigneeId, string CreatedAt, string? CompletedAt);
internal sealed record AccountWorkflowAction(string Id, string InstanceId, string? ActorId, string Action, string Note, string? FromAssigneeId, string? ToAssigneeId, string CreatedAt);
internal sealed record AccountNumberRule(string Id, string Name, string Separator, bool Active, string? CreatedBy, string UpdatedAt, IReadOnlyList<AccountNumberSegment> Segments);
internal sealed record AccountNumberSegment(string Id, string Type, string Value, int Width, int Position);

internal sealed class AdminAccountBatchSnapshotLoader(IRelationalConnectionFactory connectionFactory)
{
    public async Task<AdminAccountBatchSnapshot> LoadAsync(CancellationToken cancellationToken)
    {
        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        var schools = await QueryAsync(connection, "SELECT id, name, code, address, is_source_school, is_admission_school, active FROM schools ORDER BY name, id", reader => new AdminSchool(Text(reader, "id"), Text(reader, "name"), Text(reader, "code"), Optional(reader, "address") ?? "", Boolean(reader, "is_source_school"), Boolean(reader, "is_admission_school"), Boolean(reader, "active")), cancellationToken);
        var classes = await QueryAsync(connection, "SELECT id, school_id, name, grade, active FROM school_classes ORDER BY school_id, grade, name, id", reader => new AdminClass(Text(reader, "id"), Text(reader, "school_id"), Text(reader, "name"), Text(reader, "grade"), Boolean(reader, "active")), cancellationToken);
        var users = await QueryAsync(connection, "SELECT id, username, role, admin_level, school_id, class_id, display_name, candidate_number, active, must_change_password, totp_enabled, archived_at, created_at FROM users ORDER BY created_at, id", reader => new AdminUser(Text(reader, "id"), Text(reader, "username"), Text(reader, "role"), Optional(reader, "admin_level"), Optional(reader, "school_id"), Optional(reader, "class_id"), Text(reader, "display_name"), Optional(reader, "candidate_number"), Boolean(reader, "active"), Boolean(reader, "must_change_password"), Boolean(reader, "totp_enabled"), Optional(reader, "archived_at"), Text(reader, "created_at")), cancellationToken);
        var batches = await QueryAsync(connection, "SELECT id, school_id, requested_by, status, review_note, created_at, reviewed_at FROM candidate_account_batches ORDER BY created_at DESC, id", reader => new AccountBatch(Text(reader, "id"), Text(reader, "school_id"), Optional(reader, "requested_by"), Text(reader, "status"), Optional(reader, "review_note") ?? "", Text(reader, "created_at"), Optional(reader, "reviewed_at")), cancellationToken);
        var items = await QueryAsync(connection, "SELECT id, batch_id, class_id, position, candidate_number, initial_password, user_id, created_at FROM candidate_account_batch_items ORDER BY batch_id, position, id", reader => new AccountBatchItem(Text(reader, "id"), Text(reader, "batch_id"), Text(reader, "class_id"), Integer(reader, "position"), Optional(reader, "candidate_number") ?? "", Optional(reader, "initial_password") ?? "", Optional(reader, "user_id"), Optional(reader, "created_at")), cancellationToken);
        var steps = await QueryAsync(connection, "SELECT id, workflow_id, name, admin_level, position FROM workflow_steps ORDER BY workflow_id, position, id", reader => new StepRow(Text(reader, "workflow_id"), new AccountWorkflowStep(Text(reader, "id"), Text(reader, "name"), Text(reader, "admin_level"), Integer(reader, "position"))), cancellationToken);
        var byWorkflow = steps.GroupBy(item => item.WorkflowId).ToDictionary(group => group.Key, group => (IReadOnlyList<AccountWorkflowStep>)group.Select(item => item.Step).ToArray(), StringComparer.Ordinal);
        var workflows = await QueryAsync(connection, "SELECT id, business_type, name, active, updated_by, updated_at FROM workflow_definitions ORDER BY business_type, id", reader => { var id = Text(reader, "id"); return new AccountWorkflow(id, Text(reader, "business_type"), Text(reader, "name"), Boolean(reader, "active"), Optional(reader, "updated_by"), Text(reader, "updated_at"), byWorkflow.GetValueOrDefault(id) ?? []); }, cancellationToken);
        var instances = await QueryAsync(connection, "SELECT id, workflow_id, business_type, business_id, status, current_step, assignee_id, created_at, completed_at FROM workflow_instances ORDER BY created_at DESC, id", reader => new AccountWorkflowInstance(Text(reader, "id"), Text(reader, "workflow_id"), Text(reader, "business_type"), Text(reader, "business_id"), Text(reader, "status"), Integer(reader, "current_step"), Optional(reader, "assignee_id"), Text(reader, "created_at"), Optional(reader, "completed_at")), cancellationToken);
        var actions = await QueryAsync(connection, "SELECT id, instance_id, actor_id, action, note, from_assignee_id, to_assignee_id, created_at FROM workflow_actions ORDER BY created_at, id", reader => new AccountWorkflowAction(Text(reader, "id"), Text(reader, "instance_id"), Optional(reader, "actor_id"), Text(reader, "action"), Optional(reader, "note") ?? "", Optional(reader, "from_assignee_id"), Optional(reader, "to_assignee_id"), Text(reader, "created_at")), cancellationToken);
        var segments = await QueryAsync(connection, "SELECT id, rule_id, type, value, width, position FROM number_rule_segments ORDER BY rule_id, position, id", reader => new SegmentRow(Text(reader, "rule_id"), new AccountNumberSegment(Text(reader, "id"), Text(reader, "type"), Optional(reader, "value") ?? "", Integer(reader, "width"), Integer(reader, "position"))), cancellationToken);
        var byRule = segments.GroupBy(item => item.RuleId).ToDictionary(group => group.Key, group => (IReadOnlyList<AccountNumberSegment>)group.Select(item => item.Segment).ToArray(), StringComparer.Ordinal);
        var rules = await QueryAsync(connection, "SELECT id, name, `separator`, active, created_by, updated_at FROM number_rules ORDER BY updated_at DESC, id", reader => { var id = Text(reader, "id"); return new AccountNumberRule(id, Text(reader, "name"), Text(reader, "separator"), Boolean(reader, "active"), Optional(reader, "created_by"), Text(reader, "updated_at"), byRule.GetValueOrDefault(id) ?? []); }, cancellationToken);
        var profiles = await QueryAsync(connection, "SELECT gender, school_id FROM candidate_profiles ORDER BY updated_at, id", reader => new PreviewProfile(Optional(reader, "gender") ?? "", Optional(reader, "school_id")), cancellationToken);
        var preview = profiles.FirstOrDefault();
        return new(schools, classes, users, batches, items, workflows, instances, actions, rules, preview?.Gender ?? "女", preview?.SchoolId ?? schools.FirstOrDefault()?.Id);
    }

    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 bool Boolean(DbDataReader reader, string name) => Convert.ToInt64(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture) != 0;
    private static int Integer(DbDataReader reader, string name) => Convert.ToInt32(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture);
    private sealed record StepRow(string WorkflowId, AccountWorkflowStep Step);
    private sealed record SegmentRow(string RuleId, AccountNumberSegment Segment);
    private sealed record PreviewProfile(string Gender, string? SchoolId);
}
