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

namespace CalledIt.Domain.Services;

public class TeamService : ITeamService
{
    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 TeamService(CalledItDbContext dbContext, IOptions<UploadOptions> uploadOptions)
    {
        _dbContext = dbContext;
        _uploadDir = Path.Combine(uploadOptions.Value.BasePath, "teams");
    }

    public async Task<TeamResponseDto> CreateAsync(CreateTeamDto dto)
    {
        var nameTaken = await _dbContext.Teams.AnyAsync(t => t.Name == dto.Name);

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

        var entity = new TeamEntity { Name = dto.Name };

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

        return new TeamResponseDto(entity.Id, entity.Name, null);
    }

    public async Task<IReadOnlyList<TeamResponseDto>> GetAllAsync(int? competitionId = null)
    {
        IQueryable<TeamEntity> query = _dbContext.Teams.AsNoTracking();

        if (competitionId.HasValue)
        {
            query = query.Where(t =>
                _dbContext.Matches.Any(m => m.CompetitionId == competitionId.Value &&
                    (m.HomeTeamId == t.Id || m.AwayTeamId == t.Id)));
        }

        return await query
            .OrderBy(t => t.Name)
            .Select(t => new TeamResponseDto(t.Id, t.Name, t.LogoFileName != null ? "/uploads/teams/" + t.LogoFileName : null))
            .ToListAsync();
    }

    public async Task<TeamResponseDto> GetByIdAsync(int id)
    {
        var result = await _dbContext.Teams
            .AsNoTracking()
            .Where(t => t.Id == id)
            .Select(t => new TeamResponseDto(t.Id, t.Name, t.LogoFileName != null ? "/uploads/teams/" + t.LogoFileName : null))
            .FirstOrDefaultAsync();

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

        return result;
    }

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

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

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

        entity.Name = dto.Name;
        await _dbContext.SaveChangesAsync();

        var logoUrl = entity.LogoFileName != null ? $"/uploads/teams/{entity.LogoFileName}" : null;
        return new TeamResponseDto(entity.Id, entity.Name, logoUrl);
    }

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

        var hasMatches = await _dbContext.Matches
            .AnyAsync(m => m.HomeTeamId == id || m.AwayTeamId == id);

        if (hasMatches)
        {
            throw new InvalidOperationException("Cannot delete a team that has associated matches.");
        }

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

    public async Task<TeamResponseDto> 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.Teams.FindAsync(id)
            ?? throw new KeyNotFoundException($"Team with ID {id} not found.");

        Directory.CreateDirectory(_uploadDir);

        var fileName = $"team-{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 TeamResponseDto(entity.Id, entity.Name, $"/uploads/teams/{fileName}");
    }
}
