using Api.Database;
using Api.Database.Entities;
using Api.Extensions;
using Api.Mapping;
using Api.Services.Interfaces;
using Common.Dtos.Common;
using Common.Dtos.Subject;
using Common.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;

namespace Api.Services.Implementation;

public class SubjectService(SanStudentContext context, IDistributedCache cache)
    : ISubjectService, IServiceHelper<CreateSubjectRequest>
{
    private readonly SanStudentContext _context = context;
    private readonly IDistributedCache _cache = cache;
    private List<string> _cacheKeys = [];

    public async Task<CreateSubjectResponse> CreateAsync(CreateSubjectRequest request)
    {
        try
        {
            var problems = await Validate(request);
            if (problems.Errors.Count > 0)
            {
                return new CreateSubjectResponse
                {
                    Ok = false,
                    CreateSubjectResult = string.Join(", ", problems.Errors)
                };
            }

            var subject = new Subject()
            {
                Name = request.Name!.Trim(),
                ShortName = request.ShortName!.Trim()
            };

            _context.Subjects.Add(subject);
            await _context.SaveChangesAsync();
            await RemoveCache();
            return new CreateSubjectResponse
            {
                Ok = true,
                CreateSubjectResult = $"Zapisano nowy przedmiot \"{subject.Name}\"",
            };
        }
        catch (Exception error)
        {
            return new CreateSubjectResponse
            {
                Ok = false,
                CreateSubjectResult = $"Błąd podczas próby zapisu przedmiotu: {error.Message}",
            };
        }
    }

    public async Task<SubjectDto[]> GetAllAsync()
    {
        var key = "subjects";
        AddKeyToCache(key);
        var cachedSubjects = await _cache.GetStringAsync(key);
        if (!string.IsNullOrEmpty(cachedSubjects))
            return cachedSubjects.FromJson<SubjectDto[]>();

        var query = _context.Subjects as IQueryable<Subject>;
        var result = await query
            .OrderBy(s => s.Name)
            .ThenBy(s => s.ShortName)
            .Select(s => s.ToSubjectDto()).ToArrayAsync();

        await _cache.SetWithExpirationTimeAsync(key, result, 262487);
        return result;
    }

    public async Task<PagedList<SubjectDto>> GetAllPagedAsync(GetSubjectsRequest request)
    {
        var key = $"subjects_{request.PageNumber}_{request.PageSize}";
        if (!string.IsNullOrWhiteSpace(request.Name))
            key += $"_{request.Name.ToLower()}";

        AddKeyToCache(key);
        var cachedSubjects = await _cache.GetStringAsync(key);
        if (!string.IsNullOrEmpty(cachedSubjects))
            return cachedSubjects.FromJson<PagedList<SubjectDto>>();

        var query = _context.Subjects as IQueryable<Subject>;

        if (!string.IsNullOrEmpty(request.Name))
            query = query.Where(s => s.Name.ToLower().StartsWith(request.Name.ToLower()));

        var data = query
            .OrderBy(s => s.Name)
            .ThenBy(s => s.ShortName)
            .Select(s => s.ToSubjectDto());

        var result = await data.ToPagedListAsync(request.PageNumber, request.PageSize);
        await _cache.SetWithExpirationTimeAsync(key, result, 262487);
        return result;
    }


    public async Task<ValidationProblems> Validate(CreateSubjectRequest request)
    {
        var problems = new ValidationProblems();

        if (string.IsNullOrWhiteSpace(request.Name))
            problems.Errors.Add("Brak nazwy przedmiotu");

        if (string.IsNullOrWhiteSpace(request.ShortName))
            problems.Errors.Add("Brak nazwy skróconej przedmiotu");

        if (problems.Errors.Count > 0)
            return problems;

        var subject = await _context.Subjects
            .FirstOrDefaultAsync(s => s.Name.ToLower() == request.Name!.ToLower());

        if (subject is not null)
            problems.Errors.Add("Przedmiot o podanej nazwie już istnieje");

        subject = await _context.Subjects
            .FirstOrDefaultAsync(s => s.ShortName.ToLower() == request.ShortName!.ToLower());

        if (subject is not null)
            problems.Errors.Add("Przedmiot o podanej nazwie skróconej już istnieje");

        return problems;
    }

    public void AddKeyToCache(string key)
    {
        if (!_cacheKeys.Contains(key))
            _cacheKeys.Add(key);
    }

    public async Task RemoveCache()
    {
        foreach (var cacheKey in _cacheKeys)
            await _cache.RemoveAsync(cacheKey);
    }
}
