using System;
using System.IO;
using StellaOps.Cryptography;

namespace StellaOps.Authority.Plugin.Standard;

internal sealed class StandardPluginOptions
{
    public BootstrapUserOptions? BootstrapUser { get; set; }

    public PasswordPolicyOptions PasswordPolicy { get; set; } = new();

    public LockoutOptions Lockout { get; set; } = new();

    public TokenSigningOptions TokenSigning { get; set; } = new();

    public PasswordHashOptions PasswordHashing { get; set; } = new();

    public void Normalize(string configPath)
    {
        TokenSigning.Normalize(configPath);
    }

    public void Validate(string pluginName)
    {
        BootstrapUser?.Validate(pluginName);
        PasswordPolicy.Validate(pluginName);
        Lockout.Validate(pluginName);
        PasswordHashing.Validate();
    }
}

internal sealed class BootstrapUserOptions
{
    public string? Username { get; set; }

    public string? Password { get; set; }

    public bool RequirePasswordReset { get; set; } = true;

    public bool IsConfigured => !string.IsNullOrWhiteSpace(Username) && !string.IsNullOrWhiteSpace(Password);

    public void Validate(string pluginName)
    {
        var hasUsername = !string.IsNullOrWhiteSpace(Username);
        var hasPassword = !string.IsNullOrWhiteSpace(Password);

        if (hasUsername ^ hasPassword)
        {
            throw new InvalidOperationException($"Standard plugin '{pluginName}' requires both bootstrapUser.username and bootstrapUser.password when configuring a bootstrap user.");
        }
    }
}

internal sealed class PasswordPolicyOptions
{
    public int MinimumLength { get; set; } = 12;

    public bool RequireUppercase { get; set; } = true;

    public bool RequireLowercase { get; set; } = true;

    public bool RequireDigit { get; set; } = true;

    public bool RequireSymbol { get; set; } = true;

    public void Validate(string pluginName)
    {
        if (MinimumLength <= 0)
        {
            throw new InvalidOperationException($"Standard plugin '{pluginName}' requires passwordPolicy.minimumLength to be greater than zero.");
        }
    }

    public bool IsWeakerThan(PasswordPolicyOptions other)
    {
        if (other is null)
        {
            return false;
        }

        if (MinimumLength < other.MinimumLength)
        {
            return true;
        }

        if (!RequireUppercase && other.RequireUppercase)
        {
            return true;
        }

        if (!RequireLowercase && other.RequireLowercase)
        {
            return true;
        }

        if (!RequireDigit && other.RequireDigit)
        {
            return true;
        }

        if (!RequireSymbol && other.RequireSymbol)
        {
            return true;
        }

        return false;
    }
}

internal sealed class LockoutOptions
{
    public bool Enabled { get; set; } = true;

    public int MaxAttempts { get; set; } = 5;

    public int WindowMinutes { get; set; } = 15;

    public TimeSpan Window => TimeSpan.FromMinutes(WindowMinutes <= 0 ? 15 : WindowMinutes);

    public void Validate(string pluginName)
    {
        if (Enabled && MaxAttempts <= 0)
        {
            throw new InvalidOperationException($"Standard plugin '{pluginName}' requires lockout.maxAttempts to be greater than zero when lockout is enabled.");
        }

        if (Enabled && WindowMinutes <= 0)
        {
            throw new InvalidOperationException($"Standard plugin '{pluginName}' requires lockout.windowMinutes to be greater than zero when lockout is enabled.");
        }
    }
}

internal sealed class TokenSigningOptions
{
    public string? KeyDirectory { get; set; }

    public void Normalize(string configPath)
    {
        if (string.IsNullOrWhiteSpace(KeyDirectory))
        {
            KeyDirectory = null;
            return;
        }

        var resolved = KeyDirectory.Trim();
        if (string.IsNullOrEmpty(resolved))
        {
            KeyDirectory = null;
            return;
        }

        resolved = Environment.ExpandEnvironmentVariables(resolved);

        if (!Path.IsPathRooted(resolved))
        {
            var baseDirectory = Path.GetDirectoryName(configPath);
            if (string.IsNullOrEmpty(baseDirectory))
            {
                baseDirectory = Directory.GetCurrentDirectory();
            }

            resolved = Path.Combine(baseDirectory, resolved);
        }

        KeyDirectory = Path.GetFullPath(resolved);
    }
}
