using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace Dacryptero.Web.Events.Receive;

public class AccountLogin : ReceiveBase
{
    public string Username { get; private set; } = "";

    public string Password { get; private set; } = "";

    public override async Task Execute(ExecuteArgs args)
    {
        if (args.Connection.FidoLogin is not Session.Fido.PreLoginUser login)
        {
            await args.Connection.Send(new Send.FidoResult(
                "No active login found. Try to re-open the login screen."
            )).ConfigureAwait(false);
            return;
        }

        var access = ValidateController(login.Controller).ToList();

        if (access.Count == 0)
        {
            await args.Connection.Send(new Send.FidoResult(
                "Invalid credentials. No matching access found for your username/password combination in any of the selected databases."
            )).ConfigureAwait(false);
            return;
        }

        var user = new Session.Fido.LoginUser(login, access, this);

        if (Config.Default.Security.Tfa)
        {
            args.Connection.FidoLogin = user;

            var (command, value) = user.GetMessage();
            await args.Connection.Send(new Send.AccountFidoRequest(command, login.Name, value))
                .ConfigureAwait(false);
        }
        else
        {
            var error = await user.Unlock();
            if (error != null)
            {
                args.SendNotification(Notifications.Notification.Error(
                    $"[UNSECURE] login failed",
                    error
                ));
            }
            else
            {
                await args.Connection.Send(new Send.FidoResult())
                    .ConfigureAwait(false);
                args.SendNotification(Notifications.Notification.TfaWarning("login"));
            }
        }
    }

    private IEnumerable<(DB.DBSingleController controller, DB.DBAccess access)> ValidateController(
        IEnumerable<DB.DBSingleController> controllers
    )
    {
        foreach (var controller in controllers)
        {
            foreach (var access in controller.Config.Access)
            {
                if (access.User != Username)
                    continue;
                
                Memory<byte> pwPreData = Encoding.UTF8.GetBytes(Password);
                Memory<byte> pwData = new byte[pwPreData.Length + access.PasswordSalt.Length];
                pwPreData.CopyTo(pwData[..pwPreData.Length]);
                access.PasswordSalt.CopyTo(pwData[pwPreData.Length..]);
                Memory<byte> pwHash = SHA512.HashData(pwData.Span);

                if (pwHash.Length != access.PasswordHash.Length)
                    continue;
                if (!pwHash.Span.StartsWith(access.PasswordHash.Span))
                    continue;
                
                yield return (controller, access);
            }
        }
    }

    public override void ReadJsonContent(JsonElement json)
    {
        Username = json.GetProperty("username").GetString() ?? "";
        Password = json.GetProperty("password").GetString() ?? "";
    }
}