using Backend.Domain;
using Backend.Security;
using Microsoft.EntityFrameworkCore;

namespace Backend.Data;

public static class TeamAccountBootstrapper
{
    private const string SeedActor = "system:team-seed";

    public static async Task EnsureAsync(AwardsDbContext db, IConfiguration configuration)
    {
        var ownerSeed = BuildSeed(
            configuration,
            sectionName: "Owner",
            environmentPrefix: "VTSA_TEAM_OWNER",
            defaultLogin: "jayuhime",
            defaultDisplayName: "Jayuhime",
            role: AdminRoles.Owner,
            fallbackPassword: null);

        var creatorSeed = BuildSeed(
            configuration,
            sectionName: "Creator",
            environmentPrefix: "VTSA_TEAM_CREATOR",
            defaultLogin: "sleepy_bao",
            defaultDisplayName: "sleepy_bao",
            role: AdminRoles.Creator,
            fallbackPassword: null);

        foreach (var seed in new[] { ownerSeed, creatorSeed })
        {
            await EnsureMemberAsync(db, seed);
        }

        await DeactivateDemoBackedSeedAccountAsync(
            db,
            ownerSeed,
            configuration["VTSA_DEMO_ADMIN_PASSWORD"] ?? configuration["DemoAdmin:Password"]);

        await db.SaveChangesAsync();
    }

    private static async Task EnsureMemberAsync(AwardsDbContext db, TeamAccountSeed seed)
    {
        if (seed.Credentials is null)
        {
            return;
        }

        var member = await db.TeamMembers.FirstOrDefaultAsync(item => item.Login == seed.Login);
        if (member is null)
        {
            db.TeamMembers.Add(new TeamMember
            {
                Login = seed.Login,
                DisplayName = seed.DisplayName,
                Role = seed.Role,
                PasswordHash = seed.Credentials.Value.Hash,
                PasswordSalt = seed.Credentials.Value.Salt,
                MustChangePassword = false,
                IsActive = true,
                CreatedByTwitchId = SeedActor,
                CreatedAt = DateTimeOffset.UtcNow,
            });
            return;
        }

        var changed = false;
        if (!string.Equals(member.DisplayName, seed.DisplayName, StringComparison.Ordinal))
        {
            member.DisplayName = seed.DisplayName;
            changed = true;
        }

        if (!string.Equals(member.Role, seed.Role, StringComparison.Ordinal))
        {
            member.Role = seed.Role;
            changed = true;
        }

        if (!member.IsActive)
        {
            member.IsActive = true;
            changed = true;
        }

        if (!string.Equals(member.PasswordHash, seed.Credentials.Value.Hash, StringComparison.Ordinal)
            || !string.Equals(member.PasswordSalt, seed.Credentials.Value.Salt, StringComparison.Ordinal))
        {
            member.PasswordHash = seed.Credentials.Value.Hash;
            member.PasswordSalt = seed.Credentials.Value.Salt;
            member.MustChangePassword = false;
            changed = true;
        }

        if (changed)
        {
            member.UpdatedAt = DateTimeOffset.UtcNow;
            member.UpdatedByTwitchId = SeedActor;
        }
    }

    private static async Task DeactivateDemoBackedSeedAccountAsync(AwardsDbContext db, TeamAccountSeed seed, string? demoPassword)
    {
        if (seed.Credentials is not null || string.IsNullOrWhiteSpace(demoPassword))
        {
            return;
        }

        var member = await db.TeamMembers.FirstOrDefaultAsync(item => item.Login == seed.Login);
        if (member is null
            || member.CreatedByTwitchId != SeedActor
            || !DemoCredentialHasher.VerifyPassword(demoPassword, member.PasswordHash, member.PasswordSalt))
        {
            return;
        }

        member.IsActive = false;
        member.UpdatedAt = DateTimeOffset.UtcNow;
        member.UpdatedByTwitchId = SeedActor;
    }

    private static TeamAccountSeed BuildSeed(
        IConfiguration configuration,
        string sectionName,
        string environmentPrefix,
        string defaultLogin,
        string defaultDisplayName,
        string role,
        string? fallbackPassword)
    {
        var login = ReadSetting(configuration, sectionName, environmentPrefix, "Login") ?? defaultLogin;
        var displayName = ReadSetting(configuration, sectionName, environmentPrefix, "DisplayName") ?? defaultDisplayName;

        return new TeamAccountSeed(
            NormalizeLogin(login),
            displayName.Trim(),
            role,
            ResolveCredentials(configuration, sectionName, environmentPrefix, fallbackPassword));
    }

    private static TeamSeedCredentials? ResolveCredentials(
        IConfiguration configuration,
        string sectionName,
        string environmentPrefix,
        string? fallbackPassword)
    {
        var configuredHash = ReadSetting(configuration, sectionName, environmentPrefix, "PasswordHash");
        var configuredSalt = ReadSetting(configuration, sectionName, environmentPrefix, "PasswordSalt");
        if (!string.IsNullOrWhiteSpace(configuredHash) && !string.IsNullOrWhiteSpace(configuredSalt))
        {
            return new TeamSeedCredentials(configuredHash.Trim(), configuredSalt.Trim());
        }

        var password = ReadSetting(configuration, sectionName, environmentPrefix, "Password") ?? fallbackPassword;
        if (string.IsNullOrWhiteSpace(password))
        {
            return null;
        }

        var credentials = DemoCredentialHasher.HashPassword(password);
        return new TeamSeedCredentials(credentials.Hash, credentials.Salt);
    }

    private static string? ReadSetting(IConfiguration configuration, string sectionName, string environmentPrefix, string key)
    {
        var raw = configuration[$"{environmentPrefix}_{ToEnvironmentKey(key)}"]
            ?? configuration[$"TeamSeed:{sectionName}:{key}"];

        return string.IsNullOrWhiteSpace(raw) ? null : raw;
    }

    private static string ToEnvironmentKey(string key)
    {
        return string.Concat(key.Select((character, index) =>
            index > 0 && char.IsUpper(character) ? $"_{character}" : character.ToString())).ToUpperInvariant();
    }

    private static string NormalizeLogin(string value) =>
        value.Trim().TrimStart('@').ToLowerInvariant();

    private sealed record TeamAccountSeed(string Login, string DisplayName, string Role, TeamSeedCredentials? Credentials);

    private readonly record struct TeamSeedCredentials(string Hash, string Salt);
}
