using System.Security.Cryptography;

namespace RedundancyFinder
{
    public class Finder
    {
        Dictionary<string, Redundancy> redundancies = new Dictionary<string, Redundancy>();

        public Dictionary<string, Redundancy> Redundancies { get => redundancies; }

        string[] extensions;

        List<string> ignorePaths = new List<string>();

        public event EventHandler<DirectoryErrorEventArgs>? DirectoryError;
        public event EventHandler<FileErrorEventArgs>? FileError;
        public event EventHandler<FileFoundEventArgs>? FileFound;
        public event EventHandler<string>? TaskStarted;
        public event EventHandler<ProcessingFileEventArgs>? ProcessingFile;
        public void FindRedundancies(string[] paths, string[] extensions)
        {
            redundancies?.Values.SelectMany(x => x.Paths).ToList().ForEach(x => ignorePaths.Add(x));
            this.extensions = extensions;
            foreach (var path in paths)
            {
                if (Directory.Exists(path))
                {
                    ProcessDirectory(path);
                }
                else if (File.Exists(path))
                {
                    ProcessFile(path);
                }
            }
        }

        private void ProcessDirectory(string directoryPath)
        {
            try
            {
                // Process files in the current directory
                foreach (var file in Directory.GetFiles(directoryPath))
                {
                    ProcessFile(file);
                }

                // Recursively process subdirectories
                foreach (var subDirectory in Directory.GetDirectories(directoryPath))
                {
                    // Check if the directory is hidden and skip it if true
                    var attributes = File.GetAttributes(subDirectory);
                    if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                    {
                        continue;
                    }

                    try
                    {
                        ProcessDirectory(subDirectory);
                    }
                    catch (Exception ex)
                    {
                        DirectoryError?.Invoke(this, new DirectoryErrorEventArgs() { Exception = ex, Path = subDirectory });
                    }
                }
            }
            catch (Exception ex)
            {
                DirectoryError?.Invoke(this, new DirectoryErrorEventArgs() { Exception = ex, Path = directoryPath });
            }
        }


        private void ProcessFile(string filePath)
        {
            if(ignorePaths.Contains(filePath))
            {
                return;
            }

            if (!extensions.Contains(Path.GetExtension(filePath)))
            {
                return;
            }

            TaskStarted?.Invoke(this, filePath);
            ProcessingFile?.Invoke(this, new ProcessingFileEventArgs() { Path = filePath });

            try
            {

                var fileHash = ComputeFileHash(filePath);
                if (fileHash == null)
                {
                    return;
                }
                long fileSize = new FileInfo(filePath).Length;
                lock (redundancies)
                {
                    if (!redundancies.ContainsKey(fileHash))
                    {
                        var redundancy = new Redundancy() { Hash = fileHash, FileSize = fileSize };
                        redundancies.Add(fileHash, redundancy);
                    }
                    redundancies[fileHash].Paths.Add(filePath);
                }
                FileFound?.Invoke(this, new FileFoundEventArgs(filePath, fileHash, fileSize));
            }
            catch (Exception ex)
            {
                FileError?.Invoke(this, new FileErrorEventArgs() { Exception = ex, Path = filePath });
            }

        }

        private static string ComputeFileHash(string filePath)
        {
            using var sha256 = SHA256.Create();
            using var stream = File.OpenRead(filePath);
            var hash = sha256.ComputeHash(stream);
            return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        }
    }
}
