using CalledIt.DataAccess;
using CalledIt.DataAccess.Entities;
using CalledIt.Domain.Dtos;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace CalledIt.Domain.Services;

public class CompetitionService : ICompetitionService
{
    private static readonly Dictionary<string, string> _contentTypeToExtension = new()
    {
        ["image/jpeg"] = ".jpg",
        ["image/png"]  = ".png",
        ["image/webp"] = ".webp",
    };

    private const long MaxLogoSize = 2 * 1024 * 1024;

    private readonly CalledItDbContext _dbContext;
    private readonly string _uploadDir;

    public CompetitionService(CalledItDbContext dbContext, IOptions<UploadOptions> uploadOptions)
    {
        _dbContext = dbContext;
        _uploadDir = Path.Combine(uploadOptions.Value.BasePath, "competitions");
    }

    public async Task<CompetitionResponseDto> CreateAsync(CreateCompetitionDto dto)
    {
        var nameTaken = await _dbContext.Competitions.AnyAsync(c => c.Name == dto.Name);

        if (nameTaken)
        {
            throw new InvalidOperationException("Competition name is already taken.");
        }

        var entity = new CompetitionEntity
        {
            Name = dto.Name,
            Description = dto.Description,
            StartDate = dto.StartDate,
            EndDate = dto.EndDate
        };

        _dbContext.Competitions.Add(entity);
        await _dbContext.SaveChangesAsync();

        return new CompetitionResponseDto(entity.Id, entity.Name, entity.StartDate, entity.EndDate, entity.Description, null);
    }

    public async Task<IReadOnlyList<CompetitionResponseDto>> GetAllAsync() => await _dbContext.Competitions
        .AsNoTracking()
        .OrderBy(c => c.Name)
        .Select(c => new CompetitionResponseDto(c.Id, c.Name, c.StartDate, c.EndDate, c.Description, c.LogoFileName != null ? "/uploads/competitions/" + c.LogoFileName : null))
        .ToListAsync();

    public async Task<CompetitionResponseDto> GetByIdAsync(int id)
    {
        var result = await _dbContext.Competitions
            .AsNoTracking()
            .Where(c => c.Id == id)
            .Select(c => new CompetitionResponseDto(c.Id, c.Name, c.StartDate, c.EndDate, c.Description, c.LogoFileName != null ? "/uploads/competitions/" + c.LogoFileName : null))
            .FirstOrDefaultAsync();

        if (result == null)
        {
            throw new KeyNotFoundException($"Competition with ID {id} not found.");
        }

        return result;
    }

    public async Task<CompetitionResponseDto> UpdateAsync(int id, UpdateCompetitionDto dto)
    {
        var entity = await _dbContext.Competitions.FindAsync(id)
            ?? throw new KeyNotFoundException($"Competition with ID {id} not found.");

        var nameTaken = await _dbContext.Competitions
            .AnyAsync(c => c.Name == dto.Name && c.Id != id);

        if (nameTaken)
        {
            throw new InvalidOperationException("Competition name is already taken.");
        }

        entity.Name = dto.Name;
        entity.Description = dto.Description;
        entity.StartDate = dto.StartDate;
        entity.EndDate = dto.EndDate;

        await _dbContext.SaveChangesAsync();

        var logoUrl = entity.LogoFileName != null ? $"/uploads/competitions/{entity.LogoFileName}" : null;
        return new CompetitionResponseDto(entity.Id, entity.Name, entity.StartDate, entity.EndDate, entity.Description, logoUrl);
    }

    public async Task DeleteAsync(int id)
    {
        var entity = await _dbContext.Competitions.FindAsync(id)
            ?? throw new KeyNotFoundException($"Competition with ID {id} not found.");

        _dbContext.Competitions.Remove(entity);
        await _dbContext.SaveChangesAsync();
    }

    public async Task<CompetitionResponseDto> UploadLogoAsync(int id, Stream imageStream, string contentType, long length)
    {
        if (!_contentTypeToExtension.TryGetValue(contentType, out var ext))
        {
            throw new InvalidOperationException("Only JPEG, PNG, and WebP images are allowed.");
        }

        if (length > MaxLogoSize)
        {
            throw new InvalidOperationException("Logo file size must not exceed 2MB.");
        }

        var entity = await _dbContext.Competitions.FindAsync(id)
            ?? throw new KeyNotFoundException($"Competition with ID {id} not found.");

        Directory.CreateDirectory(_uploadDir);

        var fileName = $"competition-{id}{ext}";
        var filePath = Path.Combine(_uploadDir, fileName);

        await using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
        {
            await imageStream.CopyToAsync(fs);
        }

        entity.LogoFileName = fileName;
        await _dbContext.SaveChangesAsync();

        return new CompetitionResponseDto(entity.Id, entity.Name, entity.StartDate, entity.EndDate, entity.Description, $"/uploads/competitions/{fileName}");
    }
}
