using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using SharedKernel.Storage;
using System.Text.RegularExpressions;

namespace Infrastructure.Storage
{
    public class EditorImageService : IEditorImageService
    {
        private readonly IFileStorage _storage;
        private readonly IWebHostEnvironment _env;
        private readonly ILogger<EditorImageService> _log;

        // src="..."/src='...' 모두 커버 (단순 HTML 정규식)
        private static readonly Regex ImgSrcRegex = new Regex(@"<img\b[^>]*?\bsrc\s*=\s*(?:""(?<src>[^""]+)""|'(?<src>[^']+)')[^>]*>", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        // data:image/png;base64,AAAA...
        private static readonly Regex DataImageRegex = new Regex(@"^data:image/(?<ext>[a-zA-Z0-9.+-]+);base64,(?<data>[a-zA-Z0-9+/=\s]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        public EditorImageService(IFileStorage storage, IWebHostEnvironment env, ILogger<EditorImageService> log)
        {
            _storage = storage;
            _env = env;
            _log = log;
        }

        public async Task<string?> UploadAsync(string? html, FileStoragePath path, CancellationToken ct = default)
        {
            html ??= string.Empty;

            var basePath = path with { Target = UploadTarget.Editor };
            var baseUrlPrefix = "/" + basePath.ToRelativePath(); // "/editors/board/10/3"
            var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            var rewritten = ImgSrcRegex.Replace(html, match =>
            {
                var src = match.Groups["src"].Value;

                return match.Value;
            });

            foreach (Match m in ImgSrcRegex.Matches(rewritten))
            {
                var src = m.Groups["src"].Value;

                if (src.StartsWith("data:image/", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        var dm = DataImageRegex.Match(src);
                        if (!dm.Success)
                        {
                            continue;
                        }

                        var ext = FileUtils.NormalizeExtension(dm.Groups["ext"].Value);
                        var bytes = Convert.FromBase64String(dm.Groups["data"].Value);

                        var result = await _storage.SaveBytesAsync(bytes, ext, basePath, ct);

                        if (result != null)
                        {
                            rewritten = rewritten.Replace(src, result.Url);
                            used.Add(result.Url);
                        }

                    } catch(Exception e) {
                        _log.LogWarning(e, "Failed to persist data:image");
                    }
                } else if (FileUtils.IsLocalEditorUrl(src, baseUrlPrefix)) {
                    used.Add(src.Replace("\\", "/"));
                }
            }

            /// 미사용 파일 정리: basePath 폴더 내 파일만 대상으로
            CleanupUnusedFiles(basePath, used);

            return rewritten;
        }

        public async Task CleanupByContentAsync(string? html, CancellationToken ct = default)
        {
            if (string.IsNullOrWhiteSpace(html))
            {
                return;
            }

            foreach (Match m in ImgSrcRegex.Matches(html))
            {
                var src = m.Groups["src"].Value;
                if (!FileUtils.IsLocalPublicUrl(src)) {
                    continue;
                }

                try {
                    _storage.DeleteByUrl(src);
                } catch (Exception e) {
                    _log.LogWarning(e, "CleanupByContent delete failed: {0}", src);
                }
            }

            await Task.CompletedTask;
        }

        public void CleanupFolder(FileStoragePath path)
        {
            var basePath = path with { Target = UploadTarget.Editor };

            try
            {
                var dir = GetAbsoluteDirectory(basePath);

                if (!Directory.Exists(dir))
                {
                    return;
                }

                Directory.Delete(dir, recursive: true);
            }
            catch (Exception e)
            {
                _log.LogWarning(e, "CleanupFolder failed: {0}", basePath);
            }
        }

        private void CleanupUnusedFiles(FileStoragePath basePath, HashSet<string> usedUrls)
        {
            string dir;

            try {
                dir = GetAbsoluteDirectory(basePath);
            } catch (Exception e) {
                _log.LogWarning(e, "삭제 경로가 존재하지 않습니다.");
                return;
            }

            if (!Directory.Exists(dir))
            {
                return;
            }

            foreach (var file in Directory.GetFiles(dir))
            {
                // file => "/editors/....../filename.ext" 형태 URL로 환산
                var rel = Path.GetRelativePath(_env.WebRootPath, file).Replace("\\", "/");
                var url = "/" + rel;

                if (usedUrls.Contains(url))
                {
                    continue;
                }

                try {
                    File.Delete(file);
                }
                catch (Exception e) {
                    _log.LogWarning(e, "Failed deleting unused file: {File}", file);
                }
            }
        }

        private string GetAbsoluteDirectory(FileStoragePath path)
        {
            var relative = path.ToRelativePath();
            var webRoot = Path.GetFullPath(_env.WebRootPath);
            var fullPath = Path.GetFullPath(Path.Combine(_env.WebRootPath, relative));

            if (!fullPath.StartsWith(webRoot, StringComparison.OrdinalIgnoreCase))
            {
                throw new UnauthorizedAccessException("경로를 찾을 수 없습니다.");
            }

            return fullPath;
        }
    }
}