using CTA.Rules.Models;
using CTA.Rules.PortCore;
using Microsoft.Extensions.Logging;
using PortingAssistant.Client.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace PortingAssistant.Client.PortingProjectFile
{
    /// 
    /// Creates a handler to port projects
    /// 
    public class PortingProjectFileHandler : IPortingProjectFileHandler
    {
        private readonly ILogger _logger;
        /// 
        /// Creates an instance of a PortingProjectFileHandler
        /// 
        /// An ILogger object
        public PortingProjectFileHandler(ILogger logger)
        {
            _logger = logger;
        }
        /// 
        /// Ports a list of projects
        /// 
        /// List of projects paths
        /// Path to solution file
        /// Target framework to be used when porting
        /// List of key/value pairs where key is package and value is version number
        /// A PortingProjectFileResult object, representing the result of the porting operation
        /// 
        public List ApplyProjectChanges(
            List projects, 
            string solutionPath, 
            string targetFramework,
            Dictionary> upgradeVersions)
        {
            return ApplyProjectChanges(projects, solutionPath, targetFramework, true, upgradeVersions);
        }
        /// 
        /// Ports a list of projects
        /// 
        /// List of project paths
        /// Path to solution file
        /// Target framework to be used when porting
        /// List of key/value pairs where key is package and value is version number
        /// A PortingProjectFileResult object, representing the result of the porting operation
        /// 
        public List ApplyProjectChanges(
            List projects, 
            string solutionPath, 
            string targetFramework,
            bool includeCodeFix,
            Dictionary> upgradeVersions, VisualStudioVersion? visualStudioVersion = null)
        {
            var results = new List();
            var projectFilesNotFound = projects.Where((p) => !File.Exists(p.ProjectFilePath)).ToList();
            projectFilesNotFound.ForEach((p) => results.Add(new PortingResult
            {
                Message = "File not found.",
                ProjectFile = p.ProjectFilePath,
                ProjectName = Path.GetFileNameWithoutExtension(p.ProjectFilePath),
                Success = false
            }));
            projects = projects.Where((p) => File.Exists(p.ProjectFilePath)).ToList();
            var (projectsWithAccess, noAccessPortingResults) = VerifyFileAccess(projects);
            results.AddRange(noAccessPortingResults);
            projects = projectsWithAccess;
            _logger.LogInformation("Applying porting changes to {0}", projects.Select(p => p.ProjectFilePath).ToList());
            List configs = new List();
            projects.Where(p => !p.IsBuildFailed).ToList().ForEach((proj) =>
            {
                var upgradePackages = upgradeVersions
                    .Where(p => proj.PackageReferences
                    .Exists(package => package.PackageId == p.Key))
                    .ToDictionary(t => t.Key, t => t.Value);
                configs.Add(new PortCoreConfiguration()
                {
                    ProjectPath = proj.ProjectFilePath,
                    UseDefaultRules = true,
                    PackageReferences = upgradePackages,
                    TargetVersions = new List { targetFramework },
                    PortCode = includeCodeFix
                });
            });
            try
            {
                SolutionPort solutionPort = new SolutionPort(solutionPath, configs, _logger, visualStudioVersion: visualStudioVersion?.ToString());
                solutionPort.Run();
            }
            catch (Exception ex)
            {
                _logger.LogError($"Failed to port projects {projects.Select(p => p.ProjectFilePath).ToList()} with error: {ex}");
                configs.ForEach(config =>
                {
                    if (!projectFilesNotFound.Exists(p => p.ProjectFilePath == config.ProjectPath))
                    {
                        results.Add(new PortingResult
                        {
                            Message = $"porting project with error {ex.Message}",
                            Success = false,
                            ProjectFile = config.ProjectPath,
                            ProjectName = Path.GetFileNameWithoutExtension(config.ProjectPath)
                        });
                    }
                });
                return results;
            }
            //TODO Return result from solution run
            projects.Where(p => !projectFilesNotFound.Exists(proj => proj.ProjectFilePath == p.ProjectFilePath) && !p.IsBuildFailed)
                .ToList().ForEach((p) => results.Add(new PortingResult
                {
                    ProjectFile = p.ProjectFilePath,
                    ProjectName = Path.GetFileNameWithoutExtension(p.ProjectFilePath),
                    Success = true
                }));
            _logger.LogInformation("Completed porting changes to {0}", projects.Select(p => p.ProjectFilePath).ToList());
            return results;
        }
        /// 
        /// Checks projects to make sure we have access to the project file and at least one csharp file
        /// 
        /// List of projects to check for write access
        /// 
        /// Valid projects with access, Porting result for projects without access
        /// 
        private (List, List) VerifyFileAccess(List projects)
        {
            var noAccessPortingResults = new List();
            var projectsWithAccess = new List();
            foreach (ProjectDetails project in projects)
            {
                if (Common.Utils.FileSystemAccess.CheckWriteAccessForProject(project.ProjectFilePath))
                {
                    projectsWithAccess.Add(project);
                }
                else
                {
                    noAccessPortingResults.Add(new PortingResult
                    {
                        Success = false,
                        ProjectFile = project.ProjectFilePath,
                        ProjectName = project.ProjectName,
                        Message = $"Application does not have write access to project: {project.ProjectName}",
                        Exception = new UnauthorizedAccessException()
                    });
                }
            }
            return (projectsWithAccess, noAccessPortingResults);
        }
    }
}