using Fast.Models;
using Microsoft.AspNetCore.OData.Routing.Controllers;

namespace Fast.Web.Controllers;

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class ReportFilterController : ODataController
{
    private readonly MyDbContext db;
    private readonly AuthorizationHelper authHelper;

    public ReportFilterController(MyDbContext context)
    {
        db = context;
        authHelper = new AuthorizationHelper(Main.IsAuthenticationEnabled);
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetFilters(int reportId)
    {
        var userId = Util.GetAppUserId(User);

        var filters = await (
            from q in db.ReportFilters
            where q.ReportId == reportId &&
            q.UserId == userId
            select new
            {
                q.Id,
                q.Name,
                q.IsSelected
            }
        ).AsNoTracking().ToListAsync();

        return Ok(filters);
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> AddFilter(int reportId, string name)
    {
        var newFilter = new ReportFilter
        {
            ReportId = reportId,
            Name = name,
            UserId = Util.GetAppUserId(User),
            IsSelected = false
        };

        db.ReportFilters.Add(newFilter);
        await db.SaveChangesAsync();

        return Ok(newFilter.Id);
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> SaveFilter(int reportId, int? filterId, List<FilterParameterItem> filterValues)
    {
        if (filterId.HasValue)
        {
            var invalidItems = filterValues.Where(x => 
                (x.Value1 != null && x.Value1.Contains("NaN")) || 
                (x.Preview != null && x.Preview.Contains("Not Found"))
            ).ToList();

            if (invalidItems.Any())
            {
                var invalidNames = string.Join(", ", invalidItems.Select(x => x.Name));
                var errorMessage = $"""
                    Cannot save filter: Invalid data detected in {invalidNames}.
                    This may indicate that data was still loading when save was attempted.
                """;
                return BadRequest(new { error = errorMessage });
            }

            var paramsToRemove = await db.ReportFilterParameters.Where(x => x.FilterId == filterId).ToListAsync();
            if (paramsToRemove.Any())
                db.ReportFilterParameters.RemoveRange(paramsToRemove);

            var dbParams = FilterParameterItem.ConvertParamItemsToDbParams<ReportFilterParameter>(filterValues);
            db.ReportFilterParameters.AddRange(dbParams);
            await db.SaveChangesAsync();
        }

        var userId = Util.GetAppUserId(User);
        await SetFilterSelectedInternal(reportId, userId, filterId);

        return Ok();
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetFilterParams(int filterId)
    {
        List<FilterParameterItem> filterParams = await (
            from q in db.ReportFilterParameters
            where q.FilterId == filterId
            select FilterParameterItem.ConvertDbParamToParamItem(q)
        ).ToListAsync();

        return Ok(filterParams);
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> SetFilterSelected(int reportId, int? filterId)
    {
        var userId = Util.GetAppUserId(User);
        await SetFilterSelectedInternal(reportId, userId, filterId);
        return Ok();
    }

    private async Task SetFilterSelectedInternal(int reportId, int userId, int? filterId)
    {
        //set isSelected to false for all other filters of this report/user
        FormattableString sql = @$"update report_filter set is_selected = false
                where report_id = {reportId} and user_id = {userId}";
        await db.Database.ExecuteSqlInterpolatedAsync(sql);

        if (filterId.HasValue)
            (await db.ReportFilters.Where(x => x.Id == filterId).FirstAsync()).IsSelected = true;

        await db.SaveChangesAsync();
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> RenameFilter(int id, string name)
    {
        (await db.ReportFilters.Where(x => x.Id == id).FirstAsync()).Name = name;
        await db.SaveChangesAsync();

        return Ok(name);
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]/{id}")]
    public async Task<IActionResult> DeleteFilter(int id)
    {
        var item = await db.ReportFilters.Where(x => x.Id == id).FirstAsync();
        db.Entry(item).State = EntityState.Deleted;
        await db.SaveChangesAsync();

        return Ok();
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> CopyFilter(int id, string name)
    {
        var filterToCopy = await db.ReportFilters.Include(x => x.ReportFilterParameters).Where(x => x.Id == id).FirstAsync();

        var newFilter = new ReportFilter
        {
            ReportId = filterToCopy.ReportId,
            Name = name,
            UserId = Util.GetAppUserId(User),
            IsSelected = false,
        };

        var newFilterParams = new List<ReportFilterParameter>();
        foreach (var paramToCopy in filterToCopy.ReportFilterParameters)
        {
            var newFilterParam = new ReportFilterParameter
            {
                Name = paramToCopy.Name,
                DataSourceId = paramToCopy.DataSourceId,
                Preview = paramToCopy.Preview,
                Value1 = paramToCopy.Value1,
                Value2 = paramToCopy.Value2
            };
            newFilterParams.Add(newFilterParam);
        }

        newFilter.ReportFilterParameters = newFilterParams;

        db.ReportFilters.Add(newFilter);
        await db.SaveChangesAsync();

        return Ok(newFilter.Id);
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> GetDataSources(int reportId)
    {
        var dataSourcesIds = (await (
            from q in db.Reports
            where q.Id == reportId
            select q.DataSourceIds
        ).AsNoTracking().FirstAsync()).Split(",").Select(x => int.Parse(x));

        var dataSources = await (
            from q in db.ReportDataSources
            where dataSourcesIds.Contains(q.Id)
            orderby q.Name
            select q
        ).AsNoTracking().ToListAsync();

        return Ok(dataSources);
    }

    [Permission("Report Center", PermissionType.View)]
    [Route("[action]")]
    public async Task<IActionResult> CopyFilterToAllUsers(int filterId)
    {
        //get all users that have security for this report or report modify permission
        var reportSecurityUserIds = new List<int>();
        var reportSecurityGroupIds = new List<int>();
        var reportSecurityGroupUserIds = new List<int>();

        var filterToCopy = await db.ReportFilters.Include(x => x.ReportFilterParameters).Where(x => x.Id == filterId).AsNoTracking().FirstAsync();
        var report = await db.Reports.Where(x => x.Id == filterToCopy.ReportId).AsNoTracking().FirstAsync();

        if (!string.IsNullOrWhiteSpace(report.SecurityUserIds))
            reportSecurityUserIds = report.SecurityUserIds.Split(",").Select(x => int.Parse(x)).ToList();

        if (!string.IsNullOrWhiteSpace(report.SecurityGroupIds))
        {
            reportSecurityGroupIds = report.SecurityGroupIds.Split(",").Select(x => int.Parse(x)).DefaultIfEmpty().ToList();
            reportSecurityGroupUserIds = await (
                from q in db.SecurityUserGroups
                where reportSecurityGroupIds.Contains(q.SecurityGroupId)
                select q.UserId
            ).ToListAsync();
        }

        var permissionUserIds = await authHelper.GetAllUserInfoIdsForPermissionAsync("Report Center", PermissionType.Modify);

        List<int> userIds = reportSecurityUserIds.Concat(reportSecurityGroupUserIds).Concat(permissionUserIds).Distinct().ToList();

        foreach (var userId in userIds)
        {
            if (userId != filterToCopy.UserId)
            {
                var filterToSave = await (
                    from q in db.ReportFilters
                        .Include(x => x.ReportFilterParameters)
                    where q.ReportId == filterToCopy.ReportId && q.Name == filterToCopy.Name && q.UserId == userId
                    select q
                ).FirstOrDefaultAsync();

                if (filterToSave == null)
                {
                    filterToSave = new ReportFilter() { IsSelected = false };
                    db.ReportFilters.Add(filterToSave);
                }
                else
                    db.ReportFilterParameters.RemoveRange(filterToSave.ReportFilterParameters);

                filterToSave.Name = filterToCopy.Name;
                filterToSave.UserId = userId;
                filterToSave.ReportId = filterToCopy.ReportId;

                foreach (var paramToCopy in filterToCopy.ReportFilterParameters)
                {
                    var paramToSave = new ReportFilterParameter
                    {
                        Name = paramToCopy.Name,
                        DataSourceId = paramToCopy.DataSourceId,
                        Preview = paramToCopy.Preview,
                        Value1 = paramToCopy.Value1,
                        Value2 = paramToCopy.Value2
                    };
                    filterToSave.ReportFilterParameters.Add(paramToSave);
                }
            }
        }

        await db.SaveChangesAsync();

        foreach (var userId in userIds)
        {
            if (userId != filterToCopy.UserId)
                await SetFilterSelectedIfNoneAreYet(filterToCopy.ReportId, userId);
        }

        return Ok();
    }

    private async Task SetFilterSelectedIfNoneAreYet(int reportId, int userInfoId)
    {
        var filters = await (
            from q in db.ReportFilters
            where q.ReportId == reportId &&
            q.UserId == userInfoId
            select q
        ).ToListAsync();

        var hasAnySelectedFilter = filters.Any(x => x.IsSelected);
        var filterCount = filters.Count;

        if (!hasAnySelectedFilter && filterCount > 0)
        {
            var mostRecentFilter = filters.OrderByDescending(x => x.Id).First();
            await SetFilterSelectedInternal(reportId, userInfoId, mostRecentFilter.Id);
        }
    }
}
