using System.Reflection;
using System.Text;
using System.Xml;
using MBN_STOCK_WEBVIEW.LegacyApplication;
using Microsoft.UI.Windowing;
using Windows.Graphics;
using Windows.Storage.Pickers;
using WinRT.Interop;

namespace MBN_STOCK_WEBVIEW.LegacyParityApp;

public sealed partial class FirstRunSetupWindow : Window
{
    private const string RepositoryRootMetadataKey = "SourceRepositoryRoot";
    private const string LocalBuildPropsFileName =
        "Directory.Build.local.props";

    private string? _cutsDirectory;
    private string? _resourceDirectory;
    private bool _setupRunning;
    private readonly AppWindow _appWindow;

    public FirstRunSetupWindow()
    {
        InitializeComponent();
        _appWindow = ResolveAppWindow();
        _appWindow.Resize(new SizeInt32(820, 720));
        _appWindow.Closing += OnAppWindowClosing;
    }

    private async void OnChooseCutsClicked(object sender, RoutedEventArgs e)
    {
        var selectedPath = await PickFolderAsync();
        if (selectedPath is null)
        {
            return;
        }

        _cutsDirectory = null;
        CutsPathText.Text = "선택되지 않음";
        InstallButton.IsEnabled = false;
        var validation = LegacyOperatorFolderValidator.Validate(
            LegacyOperatorSettingsFolderKind.Scene,
            selectedPath);
        if (!validation.IsValid || validation.Folder is null)
        {
            ShowWarning(
                validation.WarningMessage ??
                "선택한 Cuts 폴더를 검증할 수 없습니다.");
            return;
        }

        _cutsDirectory = validation.Folder.CanonicalPath;
        CutsPathText.Text = _cutsDirectory;
        RefreshReadyState();
    }

    private async void OnChooseResourceClicked(object sender, RoutedEventArgs e)
    {
        var selectedPath = await PickFolderAsync();
        if (selectedPath is null)
        {
            return;
        }

        _resourceDirectory = null;
        ResourcePathText.Text = "선택되지 않음";
        InstallButton.IsEnabled = false;
        var validation = LegacyOperatorFolderValidator.Validate(
            LegacyOperatorSettingsFolderKind.Resource,
            selectedPath);
        if (!validation.IsValid || validation.Folder is null)
        {
            ShowWarning(
                validation.WarningMessage ??
                "선택한 Res 폴더를 검증할 수 없습니다.");
            return;
        }

        _resourceDirectory = validation.Folder.CanonicalPath;
        ResourcePathText.Text = _resourceDirectory;
        RefreshReadyState();
    }

    private void OnInstallClicked(object sender, RoutedEventArgs e)
    {
        if (_setupRunning)
        {
            return;
        }

        if (!TryResolveRuntimeRoot(
                _cutsDirectory,
                _resourceDirectory,
                out var runtimeRoot,
                out var warningMessage))
        {
            ShowWarning(warningMessage ?? "두 폴더를 다시 확인해 주세요.");
            return;
        }

        string repositoryRoot;
        try
        {
            repositoryRoot = ResolveRepositoryRoot();
        }
        catch
        {
            ShowError(
                "현재 Git 소스 위치를 확인할 수 없습니다. Visual Studio에서 이 솔루션을 다시 빌드해 주세요.");
            return;
        }

        SetSetupRunning(true);
        SetupStatusBar.Title = "경로 저장 중";
        SetupStatusBar.Message =
            "선택한 Cuts/Res 폴더 경로를 저장하고 있습니다.";
        SetupStatusBar.Severity = InfoBarSeverity.Informational;

        try
        {
            SaveSelectedRuntimePaths(
                repositoryRoot,
                runtimeRoot!,
                _cutsDirectory!,
                _resourceDirectory!);

            SetupStatusBar.Title = "설정 완료";
            SetupStatusBar.Message =
                "폴더 경로를 저장했습니다. 이 창을 닫고 Visual Studio에서 F5를 한 번 더 누르세요.";
            SetupStatusBar.Severity = InfoBarSeverity.Success;
            InstallButton.Visibility = Visibility.Collapsed;
            CloseButton.Visibility = Visibility.Visible;
        }
        catch
        {
            ShowError(
                "폴더 경로를 저장하지 못했습니다. 폴더와 Git 소스 위치를 확인해 주세요.");
        }
        finally
        {
            SetSetupRunning(false);
        }
    }

    private void OnCloseClicked(object sender, RoutedEventArgs e) => Close();

    private void OnAppWindowClosing(
        AppWindow sender,
        AppWindowClosingEventArgs args)
    {
        if (!_setupRunning)
        {
            return;
        }

        args.Cancel = true;
        ShowWarning("설정이 끝날 때까지 창을 닫지 마세요.");
    }

    private async Task<string?> PickFolderAsync()
    {
        if (_setupRunning)
        {
            return null;
        }

        try
        {
            var picker = new FolderPicker
            {
                SuggestedStartLocation = PickerLocationId.ComputerFolder,
                ViewMode = PickerViewMode.List
            };
            picker.FileTypeFilter.Add("*");
            InitializeWithWindow.Initialize(
                picker,
                WindowNative.GetWindowHandle(this));
            var selected = await picker.PickSingleFolderAsync();
            return selected?.Path;
        }
        catch
        {
            ShowError("폴더 선택 창을 열 수 없습니다.");
            return null;
        }
    }

    private void RefreshReadyState()
    {
        if (_cutsDirectory is null || _resourceDirectory is null)
        {
            InstallButton.IsEnabled = false;
            SetupStatusBar.Title = "준비";
            SetupStatusBar.Message = "두 폴더를 모두 선택해 주세요.";
            SetupStatusBar.Severity = InfoBarSeverity.Informational;
            return;
        }

        if (!TryResolveRuntimeRoot(
                _cutsDirectory,
                _resourceDirectory,
                out _,
                out var warningMessage))
        {
            InstallButton.IsEnabled = false;
            ShowWarning(warningMessage ?? "두 폴더를 다시 확인해 주세요.");
            return;
        }

        InstallButton.IsEnabled = true;
        SetupStatusBar.Title = "준비 완료";
        SetupStatusBar.Message =
            "설정 시작을 누르면 선택한 Cuts/Res 경로를 이 PC에 적용합니다.";
        SetupStatusBar.Severity = InfoBarSeverity.Success;
    }

    private static bool TryResolveRuntimeRoot(
        string? cutsDirectory,
        string? resourceDirectory,
        out string? runtimeRoot,
        out string? warningMessage)
    {
        runtimeRoot = null;
        warningMessage = null;
        if (string.IsNullOrWhiteSpace(cutsDirectory) ||
            string.IsNullOrWhiteSpace(resourceDirectory))
        {
            warningMessage = "Cuts 폴더와 Res 폴더를 모두 선택해 주세요.";
            return false;
        }

        var cuts = Path.TrimEndingDirectorySeparator(Path.GetFullPath(cutsDirectory));
        var resource = Path.TrimEndingDirectorySeparator(
            Path.GetFullPath(resourceDirectory));
        if (!string.Equals(
                Path.GetFileName(cuts),
                "Cuts",
                StringComparison.OrdinalIgnoreCase) ||
            !string.Equals(
                Path.GetFileName(resource),
                "Res",
                StringComparison.OrdinalIgnoreCase))
        {
            warningMessage =
                "폴더 이름이 각각 Cuts와 Res인 실제 실행 자산 폴더를 선택해 주세요.";
            return false;
        }

        var cutsParent = Path.GetDirectoryName(cuts);
        var resourceParent = Path.GetDirectoryName(resource);
        if (string.IsNullOrWhiteSpace(cutsParent) ||
            !string.Equals(
                cutsParent,
                resourceParent,
                StringComparison.OrdinalIgnoreCase))
        {
            warningMessage =
                "같은 위치에 나란히 있는 Cuts 폴더와 Res 폴더를 선택해 주세요.";
            return false;
        }

        if (cutsParent.IndexOfAny(['$', '%', '@', ';', '\'']) >= 0)
        {
            warningMessage =
                "선택한 폴더 경로에 설정용 예약 문자가 포함되어 있습니다. 다른 위치의 Cuts/Res를 선택해 주세요.";
            return false;
        }

        runtimeRoot = cutsParent;
        return true;
    }

    private static string ResolveRepositoryRoot()
    {
        var metadata = typeof(FirstRunSetupWindow).Assembly
            .GetCustomAttributes<AssemblyMetadataAttribute>()
            .Single(attribute => string.Equals(
                attribute.Key,
                RepositoryRootMetadataKey,
                StringComparison.Ordinal));
        if (string.IsNullOrWhiteSpace(metadata.Value))
        {
            throw new InvalidOperationException(
                "The source repository metadata is empty.");
        }

        var repositoryRoot = Path.TrimEndingDirectorySeparator(
            Path.GetFullPath(metadata.Value));
        var project = Path.Combine(
            repositoryRoot,
            "src",
            "MBN_STOCK_WEBVIEW.LegacyParityApp",
            "MBN_STOCK_WEBVIEW.LegacyParityApp.csproj");
        if (!File.Exists(project))
        {
            throw new InvalidOperationException(
                "The source repository metadata is not usable.");
        }

        return repositoryRoot;
    }

    private static void SaveSelectedRuntimePaths(
        string repositoryRoot,
        string runtimeRoot,
        string cutsDirectory,
        string resourceDirectory)
    {
        var store = new LegacyOperatorSettingsStore();
        var loaded = store.Load();
        var current = loaded.Failure == LegacyOperatorSettingsStoreFailure.None
            ? loaded.Settings
            : LegacyOperatorSettings.Default;
        var saved = store.Save(current with
        {
            SceneDirectory = cutsDirectory,
            ResourceDirectory = resourceDirectory
        });
        if (!saved.Succeeded)
        {
            throw new InvalidOperationException(
                "The selected runtime folders could not be saved.");
        }

        SaveRuntimeBinding(repositoryRoot, runtimeRoot);
    }

    private static void SaveRuntimeBinding(
        string repositoryRoot,
        string runtimeRoot)
    {
        var destination = Path.Combine(
            repositoryRoot,
            LocalBuildPropsFileName);
        if (Directory.Exists(destination))
        {
            throw new InvalidOperationException(
                "The local runtime binding path is not a file.");
        }

        if (File.Exists(destination))
        {
            var attributes = File.GetAttributes(destination);
            if ((attributes & (FileAttributes.Directory |
                    FileAttributes.ReparsePoint |
                    FileAttributes.Device)) != 0)
            {
                throw new InvalidOperationException(
                    "The local runtime binding file is unsafe.");
            }
        }

        var bytes = CreateRuntimeBinding(runtimeRoot);
        var temporary = Path.Combine(
            repositoryRoot,
            $".{LocalBuildPropsFileName}.{Guid.NewGuid():N}.tmp");
        try
        {
            using (var stream = new FileStream(
                       temporary,
                       FileMode.CreateNew,
                       FileAccess.Write,
                       FileShare.None,
                       bufferSize: 4096,
                       FileOptions.WriteThrough))
            {
                stream.Write(bytes);
                stream.Flush(flushToDisk: true);
            }

            File.Move(temporary, destination, overwrite: true);
            temporary = string.Empty;
            if (!File.ReadAllBytes(destination).AsSpan().SequenceEqual(bytes))
            {
                throw new IOException(
                    "The local runtime binding failed verification.");
            }
        }
        finally
        {
            if (!string.IsNullOrEmpty(temporary) &&
                File.Exists(temporary))
            {
                File.Delete(temporary);
            }
        }
    }

    private static byte[] CreateRuntimeBinding(string runtimeRoot)
    {
        using var stream = new MemoryStream(capacity: 512);
        var settings = new XmlWriterSettings
        {
            Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
            Indent = true,
            NewLineChars = Environment.NewLine,
            NewLineHandling = NewLineHandling.Replace
        };
        using (var writer = XmlWriter.Create(stream, settings))
        {
            writer.WriteStartDocument();
            writer.WriteStartElement("Project");
            writer.WriteStartElement("PropertyGroup");
            writer.WriteElementString(
                "LegacyRuntimeSourceRoot",
                runtimeRoot);
            writer.WriteElementString(
                "LegacyRuntimeAssetsMode",
                "Required");
            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteEndDocument();
        }

        return stream.ToArray();
    }

    private void SetSetupRunning(bool running)
    {
        _setupRunning = running;
        ChooseCutsButton.IsEnabled = !running;
        ChooseResourceButton.IsEnabled = !running;
        InstallButton.IsEnabled = !running &&
            _cutsDirectory is not null &&
            _resourceDirectory is not null;
        SetupProgress.Visibility = running
            ? Visibility.Visible
            : Visibility.Collapsed;
    }

    private void ShowWarning(string message)
    {
        SetupStatusBar.Title = "확인 필요";
        SetupStatusBar.Message = message;
        SetupStatusBar.Severity = InfoBarSeverity.Warning;
    }

    private void ShowError(string message)
    {
        SetupStatusBar.Title = "설정 실패";
        SetupStatusBar.Message = message;
        SetupStatusBar.Severity = InfoBarSeverity.Error;
    }

    private AppWindow ResolveAppWindow()
    {
        var windowHandle = WindowNative.GetWindowHandle(this);
        var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle);
        return AppWindow.GetFromWindowId(windowId);
    }

}
