using SnykRestApi.Models.Parsed;
using SnykRestApi.Models.Raw;
using SnykRestApi.Repositories;
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SnykRestApi.Services
{
    public class AuditLogService
    {
        private readonly OrganizationRepository _organizationRepository;
        private readonly AuditLogRepository _auditLogRepository;
        private readonly ProjectRepository _projectRepository;
        private readonly UserRepository _userRepository;
        private readonly CsvRepository _csvRepository;

        public AuditLogService(OrganizationRepository organizationRepository, AuditLogRepository auditLogRepostitory, ProjectRepository projectRepository, UserRepository userRepository, CsvRepository csvRepository)
        {
            _organizationRepository = organizationRepository;
            _auditLogRepository = auditLogRepostitory;
            _projectRepository = projectRepository;
            _userRepository = userRepository;
            _csvRepository = csvRepository;
        }

        public async Task CreateAuditLog ()
        {
            var rule = new Rule("[skyblue1]Creating Snyk Audit Log CSV[/]");
            rule.Alignment = Justify.Left;
            rule.Style = Style.Parse("skyblue1");
            AnsiConsole.Write(rule);

            await AnsiConsole.Status()
                 .AutoRefresh(true)
                .Spinner(Spinner.Known.Default)
                .StartAsync("Retrieving organizations...", async ctx =>
                {
                    var organizations = await _organizationRepository.GetAll();
                    if (organizations == null || !organizations.Any()) throw new Exception("No organizations found");

                    var log = new List<AuditLogResponse>();
                    var projects = new List<ProjectResponse>();

                    foreach (var organization in organizations)
                    {
                        rule = new Rule($"[skyblue1]{organization.Name}[/]");
                        rule.Alignment = Justify.Left;
                        rule.Style = Style.Parse("skyblue1 dim");
                        AnsiConsole.Write(rule);

                        ctx.Status($"Getting the projects for organization '{organization.Name}'");
                        var orgProjects = await _projectRepository.GetAll(organization.Id);
                        projects.AddRange(orgProjects);
                        AnsiConsole.WriteLine($"Got {orgProjects.Count} projects.");

                        ctx.Status($"Getting the audit log for organization '{organization.Name}'");
                        var orgLogs = await _auditLogRepository.GetByOrganizationId(organization.Id);
                        log.AddRange(orgLogs);
                        AnsiConsole.WriteLine($"Got {orgLogs.Count} log records.");
                    }

                    rule = new Rule($"[skyblue1]Retrieving users[/]");
                    rule.Alignment = Justify.Left;
                    rule.Style = Style.Parse("skyblue1 dim");
                    AnsiConsole.Write(rule);

                    ctx.Status($"Getting users");
                    var userIds = log.Select(l => l.UserId).Distinct().ToList();
                    
                    var users = await _userRepository.GetAll(userIds);
                    AnsiConsole.WriteLine($"Got {users.Count} users of {userIds.Count} user ids.");

                    rule = new Rule($"[skyblue1]Creating CSV[/]");
                    rule.Alignment = Justify.Left;
                    rule.Style = Style.Parse("skyblue1 dim");
                    AnsiConsole.Write(rule);

                    ctx.Status($"Combining all information");
                    var result = (from l in log 
                                 join o in organizations on l.OrgId equals o.Id into gjO
                                 from subO in gjO.DefaultIfEmpty()
                                 join u in users on l.UserId equals u.Id into gjU
                                 from subU in gjU.DefaultIfEmpty()
                                 join p in projects on l.ProjectId equals p.Id into gjP
                                 from subP in gjP.DefaultIfEmpty()
                                 select new AuditLog()
                                 {
                                     GroupId = l.GroupId,
                                     OrganizationId = l.OrgId,
                                     OrganizationName = subO?.Name,
                                     ProjectId = l.ProjectId,
                                     ProjectName = subP?.Name,
                                     UserId = l.UserId,
                                     UserName = subU?.Name,
                                     Event = l.Event,
                                     Created = l.Created
                                 }).ToList();
                    AnsiConsole.WriteLine($"Prepared {result.Count} lines to export of {log.Count} audit log records.");

                    ctx.Status($"Writing CSV");
                    await _csvRepository.WriteAll(result);
                });
        }
    }
}
