using Journal.Core.Models;

namespace Journal.Core.Repositories;

public class InMemoryFragmentRepository : IFragmentRepository
{
    private readonly List<Fragment> _store = [];
    private readonly Lock _lock = new();

    public List<Fragment> GetAll()
    {
        lock (_lock)
        {
            return [.. _store];
        }
    }

    public Fragment? GetById(Guid id)
    {
        lock (_lock)
        {
            return _store.FirstOrDefault(f => f.Id == id);
        }
    }

    public void Add(Fragment fragment)
    {
        ArgumentNullException.ThrowIfNull(fragment);
        lock (_lock)
        {
            if (fragment.Tags != null)
            {
                fragment.Tags = [.. fragment.Tags
                    .Where(t => !string.IsNullOrWhiteSpace(t))
                    .Select(t => t!.Trim())];
            }
            if (!string.IsNullOrWhiteSpace(fragment.Type)) fragment.Type = fragment.Type.Trim();
            if (!string.IsNullOrWhiteSpace(fragment.Description)) fragment.Description = fragment.Description.Trim();

            _store.Add(fragment);
        }
    }

    public bool Remove(Guid id)
    {
        lock (_lock)
        {
            var item = _store.FirstOrDefault(f => f.Id == id);
            if (item is null) return false;
            return _store.Remove(item);
        }
    }

    public bool Update(Guid id, string? type = null, string? description = null, IEnumerable<string>? tags = null, DateTimeOffset? time = null)
    {
        lock (_lock)
        {
            var item = _store.FirstOrDefault(f => f.Id == id);
            if (item is null) return false;

            if (type != null)
            {
                if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Type cannot be empty", nameof(type));
                item.Type = type.Trim();
            }

            if (description != null)
            {
                if (string.IsNullOrWhiteSpace(description)) throw new ArgumentException("Description cannot be empty", nameof(description));
                item.Description = description.Trim();
            }

            if (tags != null)
            {
                item.Tags = [.. tags
                    .Where(t => !string.IsNullOrWhiteSpace(t))
                    .Select(t => t!.Trim())];
            }

            if (time.HasValue)
                item.Time = time.Value;

            return true;
        }
    }

    public List<Fragment> GetByTag(string tag)
    {
        var q = tag?.Trim();
        if (string.IsNullOrWhiteSpace(q)) return [];
        lock (_lock)
        {
            return [.. _store.Where(f => f.Tags?.Any(t => !string.IsNullOrWhiteSpace(t) && t.Contains(q, StringComparison.OrdinalIgnoreCase)) == true)];
        }
    }

    public List<Fragment> GetByType(string type)
    {
        var q = type?.Trim();
        if (string.IsNullOrWhiteSpace(q)) return [];
        lock (_lock)
        {
            return [.. _store.Where(f => !string.IsNullOrWhiteSpace(f.Type) && f.Type.Trim().Contains(q, StringComparison.OrdinalIgnoreCase))];
        }
    }

    public List<Fragment> Search(string? type = null, string? tag = null, DateTimeOffset? timeAfter = null)
    {
        var results = _store.AsEnumerable();
        var qType = type?.Trim();
        var qTag = tag?.Trim();

        lock (_lock)
        {
            if (!string.IsNullOrWhiteSpace(qType))
                results = results.Where(f => !string.IsNullOrWhiteSpace(f.Type) && f.Type.Trim().Contains(qType, StringComparison.OrdinalIgnoreCase));
            if (!string.IsNullOrWhiteSpace(qTag))
                results = results.Where(f => f.Tags?.Any(t => !string.IsNullOrWhiteSpace(t) && t.Contains(qTag, StringComparison.OrdinalIgnoreCase)) == true);
            if (timeAfter.HasValue)
                results = results.Where(f => f.Time > timeAfter.Value);

            return [.. results];
        }
    }
}
