using Modules.User.Domain.Gateways;

namespace Modules.User.Domain.Entities.Account;

public class Account
{
    public Guid Id { get; private set; }
    public Email Email { get; private set; }
    public string HashedPassword { get; private set; }

    private List<Session> _sessions = [];

    public IReadOnlyList<Session> Sessions => _sessions.AsReadOnly();

    public Account(Models.Account model)
    {
        Id = model.Id;
        Email = new Email(model.Email);
        HashedPassword = model.HashedPassword;
        _sessions = model.Sessions.Select(q => new Session(q)).ToList();
    }

    private Account(Email email, string hashedPassword)
    {
        Email = email;
        HashedPassword = hashedPassword;
    }

    public static Account Create(string email, string password, IPasswordGateway passwordHasher)
    {
        if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email));
        if (string.IsNullOrWhiteSpace(password)) throw new ArgumentNullException(nameof(password));
        var account = new Account(new Email(email), passwordHasher.HashPassword(password));
        return account;
    }

    public void SetEmail(string email)
    {
        if (string.IsNullOrWhiteSpace(email)) throw new ArgumentNullException(nameof(email));
        Email = new Email(email);
    }

    public void SetPassword(string password, IPasswordGateway passwordHasher)
    {
        var newPasswordHash = passwordHasher.HashPassword(password);
        if (newPasswordHash != HashedPassword) throw new Exception("Password must not be aqual to previous");
        HashedPassword = newPasswordHash;
    }

    public Session AddSession(TimeSpan expirationTime, IRefreshTokenGateway gateway, string? userAgent, string? country, string? city)
    {
        if (Id == Guid.Empty) throw new ArgumentNullException(nameof(Id));
        ClearExpiredSessions();
        var sessionCollisions = FindSessions(userAgent);
        var session = Session.Create(Id, expirationTime, gateway, userAgent, country, city);
        if (sessionCollisions.Any())
        {
            session.SetId(sessionCollisions.OrderBy(q => q.ExpiredDate).Last().Id);
            _sessions = _sessions.Where(q => !sessionCollisions.Contains(q)).ToList();
        }
        _sessions.Add(session);
        return session;
    }

    public string? UpdateRefreshToken(Guid sessionId, IRefreshTokenGateway gateway, TimeSpan expirationTime, string? userAgent, string? country, string? city)
    {
        ClearExpiredSessions();
        var session = _sessions.FirstOrDefault(s => s.Id == sessionId);
        if (session == null) return null;
        return session.UpdateRefreshToken(gateway, expirationTime, userAgent, country, city);
    }

    private IEnumerable<Session> FindSessions(string? userAgent) =>
        _sessions.Where(q => q.ClientInfo.UserAgent == userAgent);

    public bool DeleteSession(Guid sessionId)
    {
        var session = _sessions.FirstOrDefault(s => s.Id == sessionId);
        if (session == null) return false;
        _sessions.Remove(session);
        return true;
    }

    public void ClearExpiredSessions() =>
        _sessions = _sessions.Where(q => q.ExpiredDate >= DateTime.UtcNow).ToList();

    public void ClearAllSessions() => _sessions.Clear();
}