using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using PalladiumWallet.App.Localization;
using PalladiumWallet.Core.Chain;
using PalladiumWallet.Core.Crypto;
using PalladiumWallet.Core.Storage;
using PalladiumWallet.Core.Wallet;

namespace PalladiumWallet.App.ViewModels;

public partial class MainWindowViewModel
{
    // ---- setup wizard

    public const string StepDataLocation = "data-location";
    public const string StepStart = "start";
    public const string StepChooseWallet = "choose-wallet";
    public const string StepOpen = "open";
    public const string StepShowSeed = "show-seed";
    public const string StepConfirmSeed = "confirm-seed";
    public const string StepWords = "words";
    public const string StepPassphrase = "passphrase";
    public const string StepScriptType = "script-type";
    public const string StepImportXkey = "import-xkey";
    public const string StepImportWif = "import-wif";
    public const string StepImportAddress = "import-address";
    public const string StepPassword = "password";

    private enum WizardFlowKind { New, Restore, ImportXkey, ImportWif, ImportAddress }
    private WizardFlowKind _wizardFlow;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(IsStepDataLocation))]
    [NotifyPropertyChangedFor(nameof(IsStepStart))]
    [NotifyPropertyChangedFor(nameof(IsStepChooseWallet))]
    [NotifyPropertyChangedFor(nameof(IsStepOpen))]
    [NotifyPropertyChangedFor(nameof(IsStepShowSeed))]
    [NotifyPropertyChangedFor(nameof(IsStepConfirmSeed))]
    [NotifyPropertyChangedFor(nameof(IsStepWords))]
    [NotifyPropertyChangedFor(nameof(IsStepPassphrase))]
    [NotifyPropertyChangedFor(nameof(IsStepScriptType))]
    [NotifyPropertyChangedFor(nameof(IsStepImportXkey))]
    [NotifyPropertyChangedFor(nameof(IsStepImportWif))]
    [NotifyPropertyChangedFor(nameof(IsStepImportAddress))]
    [NotifyPropertyChangedFor(nameof(IsStepPassword))]
    private string setupStep = StepStart;

    public bool IsStepDataLocation => SetupStep == StepDataLocation;
    public bool IsStepStart        => SetupStep == StepStart;
    public bool IsStepChooseWallet => SetupStep == StepChooseWallet;
    public bool IsStepOpen         => SetupStep == StepOpen;
    public bool IsStepShowSeed     => SetupStep == StepShowSeed;
    public bool IsStepConfirmSeed  => SetupStep == StepConfirmSeed;
    public bool IsStepWords        => SetupStep == StepWords;
    public bool IsStepPassphrase   => SetupStep == StepPassphrase;
    public bool IsStepScriptType   => SetupStep == StepScriptType;
    public bool IsStepImportXkey    => SetupStep == StepImportXkey;
    public bool IsStepImportWif     => SetupStep == StepImportWif;
    public bool IsStepImportAddress => SetupStep == StepImportAddress;
    public bool IsStepPassword      => SetupStep == StepPassword;

    [ObservableProperty]
    [NotifyPropertyChangedFor(nameof(IsLegacySelected))]
    [NotifyPropertyChangedFor(nameof(IsWrappedSegwitSelected))]
    [NotifyPropertyChangedFor(nameof(IsNativeSegwitSelected))]
    [NotifyPropertyChangedFor(nameof(IsTaprootSelected))]
    private ScriptKind selectedScriptKind = ScriptKind.NativeSegwit;

    public bool IsLegacySelected        => SelectedScriptKind == ScriptKind.Legacy;
    public bool IsWrappedSegwitSelected => SelectedScriptKind == ScriptKind.WrappedSegwit;
    public bool IsNativeSegwitSelected  => SelectedScriptKind == ScriptKind.NativeSegwit;
    public bool IsTaprootSelected       => SelectedScriptKind == ScriptKind.Taproot;

    [RelayCommand] private void SelectLegacy()        => SelectedScriptKind = ScriptKind.Legacy;
    [RelayCommand] private void SelectWrappedSegwit() => SelectedScriptKind = ScriptKind.WrappedSegwit;
    [RelayCommand] private void SelectNativeSegwit()  => SelectedScriptKind = ScriptKind.NativeSegwit;
    [RelayCommand] private void SelectTaproot()       => SelectedScriptKind = ScriptKind.Taproot;

    public string DefaultDataPath => AppPaths.DefaultDataRoot();

    [ObservableProperty]
    private string importXkeyInput = "";

    [ObservableProperty]
    private string importWifInput = "";

    [ObservableProperty]
    private string importAddressInput = "";

    // Script type detected during xkey decoding (to display to the user)
    [ObservableProperty]
    private string importXkeyDetectedKind = "";

    [ObservableProperty]
    private string mnemonicInput = "";

    [ObservableProperty]
    private string confirmMnemonicInput = "";

    [ObservableProperty]
    private string passphraseInput = "";

    [ObservableProperty]
    private string walletNameInput = "";

    [ObservableProperty]
    private string passwordInput = "";

    [ObservableProperty]
    private string confirmPasswordInput = "";

    [ObservableProperty]
    private bool encryptWallet = true;

    [ObservableProperty]
    private bool walletFileExists;

    public ObservableCollection<WalletFileEntry> WalletList { get; } = [];

    [RelayCommand]
    private void UseDefaultDataLocation() => ApplyDataLocation(AppPaths.DefaultDataRoot());

    public void ApplyDataLocation(string root)
    {
        try
        {
            AppPaths.ConfigureDataLocation(root);
        }
        catch (Exception ex)
        {
            StatusMessage = $"{Loc.Tr("msg.error")}: {ex.Message}";
            return;
        }
        _config = AppConfig.Load();
        _loc = Loc.SwitchTo(_config.Language);
        OnPropertyChanged(nameof(Loc));
        OnPropertyChanged(nameof(UnitLabel));
        RefreshSetupState();
    }

    private void RefreshSetupState()
    {
        SetupStep = StepStart;
        SelectedScriptKind = ScriptKind.NativeSegwit;
        MnemonicInput = ConfirmMnemonicInput = PassphraseInput = PasswordInput = ConfirmPasswordInput = "";
        WalletFileExists = AppPaths.WalletFiles(Net).Count > 0;
        StatusMessage = "";
        RefreshServers();
        _ = ConnectAndSync();
    }

    [RelayCommand]
    private void WizardStartOpen()
    {
        var files = AppPaths.WalletFiles(Net);
        if (files.Count > 1)
        {
            WalletList.Clear();
            foreach (var path in files)
                WalletList.Add(new WalletFileEntry(Path.GetFileName(path), path));
            SetupStep = StepChooseWallet;
            StatusMessage = "";
            return;
        }
        _pendingOpenPath = files.Count == 1 ? files[0] : AppPaths.DefaultWalletPath(Net);
        PasswordInput = "";
        SetupStep = StepOpen;
        StatusMessage = "";
    }

    [RelayCommand]
    private void ChooseWallet(WalletFileEntry? entry)
    {
        if (entry is null)
            return;
        _pendingOpenPath = entry.Path;
        PasswordInput = "";
        SetupStep = StepOpen;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardStartNew()
    {
        _wizardFlow = WizardFlowKind.New;
        MnemonicInput = Bip39.Generate(MnemonicLength.Twelve).ToString();
        SetupStep = StepShowSeed;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardStartRestore()
    {
        _wizardFlow = WizardFlowKind.Restore;
        MnemonicInput = "";
        SetupStep = StepWords;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardStartImportXkey()
    {
        _wizardFlow = WizardFlowKind.ImportXkey;
        ImportXkeyInput = "";
        ImportXkeyDetectedKind = "";
        SetupStep = StepImportXkey;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardStartImportWif()
    {
        _wizardFlow = WizardFlowKind.ImportWif;
        ImportWifInput = "";
        SetupStep = StepImportWif;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardStartImportAddress()
    {
        _wizardFlow = WizardFlowKind.ImportAddress;
        ImportAddressInput = "";
        SetupStep = StepImportAddress;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardNextFromShowSeed()
    {
        ConfirmMnemonicInput = "";
        SetupStep = StepConfirmSeed;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardNextFromConfirmSeed()
    {
        var normalized = string.Join(' ',
            ConfirmMnemonicInput.Split(' ', StringSplitOptions.RemoveEmptyEntries));
        if (!string.Equals(normalized, MnemonicInput, StringComparison.OrdinalIgnoreCase))
        {
            StatusMessage = Loc.Tr("msg.seed.mismatch");
            return;
        }
        GoToPassphraseStep();
    }

    [RelayCommand]
    private void WizardNextFromWords()
    {
        if (!Bip39.TryParse(MnemonicInput, out _))
        {
            StatusMessage = Loc.Tr("msg.words.invalid");
            return;
        }
        GoToPassphraseStep();
    }

    private void GoToPassphraseStep()
    {
        PassphraseInput = "";
        SetupStep = StepPassphrase;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardNextFromPassphrase()
    {
        SetupStep = StepScriptType;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardNextFromImportXkey()
    {
        if (string.IsNullOrWhiteSpace(ImportXkeyInput))
        {
            StatusMessage = Loc.Tr("msg.xkey.required");
            return;
        }
        // Validate and detect the type: try as xpub first, then as xprv.
        if (Slip132.TryDecodePublic(ImportXkeyInput.Trim(), Profile, out _, out var pubKind))
        {
            SelectedScriptKind = pubKind;
            ImportXkeyDetectedKind = $"{pubKind} (watch-only)";
        }
        else if (Slip132.TryDecodePrivate(ImportXkeyInput.Trim(), Profile, out _, out var prvKind))
        {
            SelectedScriptKind = prvKind;
            ImportXkeyDetectedKind = prvKind.ToString();
        }
        else
        {
            StatusMessage = Loc.Tr("msg.xkey.invalid");
            return;
        }
        PasswordInput = ConfirmPasswordInput = "";
        EncryptWallet = true;
        SetupStep = StepPassword;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardNextFromImportWif()
    {
        if (string.IsNullOrWhiteSpace(ImportWifInput))
        {
            StatusMessage = Loc.Tr("msg.wif.required");
            return;
        }
        // Validate the WIF with an early parse.
        try
        {
            _ = new NBitcoin.BitcoinSecret(ImportWifInput.Trim(), PalladiumNetworks.For(Net));
        }
        catch
        {
            StatusMessage = Loc.Tr("msg.wif.invalid");
            return;
        }
        SetupStep = StepScriptType;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardNextFromImportAddress()
    {
        var addresses = ImportAddressInput.Split('\n',
            StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (addresses.Length == 0)
        {
            StatusMessage = Loc.Tr("msg.address.required");
            return;
        }
        var network = PalladiumNetworks.For(Net);
        foreach (var a in addresses)
        {
            try
            {
                _ = NBitcoin.BitcoinAddress.Create(a, network);
            }
            catch
            {
                StatusMessage = Loc.Tr("msg.address.invalid");
                return;
            }
        }
        PasswordInput = ConfirmPasswordInput = "";
        EncryptWallet = true;
        SetupStep = StepPassword;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardNextFromScriptType()
    {
        PasswordInput = ConfirmPasswordInput = "";
        EncryptWallet = true;
        SetupStep = StepPassword;
        StatusMessage = "";
    }

    [RelayCommand]
    private void WizardBack()
    {
        SetupStep = SetupStep switch
        {
            StepOpen => WalletList.Count > 1 ? StepChooseWallet : StepStart,
            StepChooseWallet or StepShowSeed or StepWords or StepImportXkey or StepImportWif or StepImportAddress => StepStart,
            StepConfirmSeed => StepShowSeed,
            StepPassphrase => _wizardFlow == WizardFlowKind.Restore ? StepWords : StepConfirmSeed,
            StepScriptType => _wizardFlow == WizardFlowKind.ImportWif ? StepImportWif : StepPassphrase,
            StepPassword => _wizardFlow switch
            {
                WizardFlowKind.ImportXkey => StepImportXkey,
                WizardFlowKind.ImportAddress => StepImportAddress,
                _ => StepScriptType,
            },
            _ => StepStart,
        };
        if (SetupStep == StepStart)
            RefreshSetupState();
    }

    [RelayCommand]
    private void CreateOrRestore()
    {
        string? password;
        if (EncryptWallet)
        {
            if (string.IsNullOrEmpty(PasswordInput))
            {
                StatusMessage = Loc.Tr("msg.password.required");
                return;
            }
            if (PasswordInput != ConfirmPasswordInput)
            {
                StatusMessage = Loc.Tr("msg.password.mismatch");
                return;
            }
            password = PasswordInput;
        }
        else
        {
            password = null;
        }

        try
        {
            WalletDocument doc;
            IWalletAccount account;

            switch (_wizardFlow)
            {
                case WizardFlowKind.ImportXkey:
                {
                    var input = ImportXkeyInput.Trim();
                    if (Slip132.TryDecodePublic(input, Profile, out _, out _))
                    {
                        var (d, a) = WalletLoader.NewFromXpub(input, Profile, SelectedScriptKind);
                        (doc, account) = (d, a);
                    }
                    else
                    {
                        var (d, a) = WalletLoader.NewFromXprv(input, Profile, SelectedScriptKind);
                        (doc, account) = (d, a);
                    }
                    break;
                }
                case WizardFlowKind.ImportWif:
                {
                    var wifLines = ImportWifInput.Split('\n',
                        StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
                    var (d, a) = WalletLoader.NewFromWif(wifLines, SelectedScriptKind, Profile);
                    (doc, account) = (d, a);
                    break;
                }
                case WizardFlowKind.ImportAddress:
                {
                    var addressLines = ImportAddressInput.Split('\n',
                        StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
                    var (d, a) = WalletLoader.NewFromAddresses(addressLines, Profile);
                    (doc, account) = (d, a);
                    break;
                }
                default:
                {
                    var (d, a) = WalletLoader.NewFromMnemonic(
                        MnemonicInput,
                        string.IsNullOrEmpty(PassphraseInput) ? null : PassphraseInput,
                        SelectedScriptKind,
                        Profile);
                    (doc, account) = (d, a);
                    break;
                }
            }

            var path = WalletPathFromName(WalletNameInput);
            if (WalletStore.Exists(path))
            {
                StatusMessage = Loc.Tr("msg.wallet.exists");
                return;
            }
            WalletStore.Save(doc, path, password);
            var newLock = WalletLock.TryAcquire(path);
            if (newLock is null) { StatusMessage = Loc.Tr("msg.wallet.locked"); return; }
            OpenLoaded(doc, account, path, password, newLock);
        }
        catch (Exception ex)
        {
            StatusMessage = $"{Loc.Tr("msg.error")}: {ex.Message}";
        }
    }

    /// <summary>
    /// Builds the file path from the name entered by the user.
    /// If the name is empty, generates an automatic name (default, wallet-2, …).
    /// Removes characters that are invalid for the filesystem.
    /// </summary>
    private string WalletPathFromName(string name)
    {
        var clean = string.IsNullOrWhiteSpace(name)
            ? ""
            : string.Concat(name.Trim()
                .Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c))
              .Trim('.');

        if (string.IsNullOrEmpty(clean))
        {
            // No name provided → auto-generated name
            var def = AppPaths.DefaultWalletPath(Net);
            for (var n = 2; WalletStore.Exists(def); n++)
                def = Path.Combine(AppPaths.WalletsDir(Net), $"wallet-{n}.wallet.json");
            return def;
        }

        return Path.Combine(AppPaths.WalletsDir(Net), $"{clean}.wallet.json");
    }

    [RelayCommand]
    private void OpenExisting()
    {
        var path = _pendingOpenPath ?? AppPaths.DefaultWalletPath(Net);
        var password = string.IsNullOrEmpty(PasswordInput) ? null : PasswordInput;

        var newLock = WalletLock.TryAcquire(path);
        if (newLock is null) { StatusMessage = Loc.Tr("msg.wallet.locked"); return; }

        try
        {
            var doc = WalletStore.Load(path, password);
            _pendingOpenPath = null;
            OpenLoaded(doc, WalletLoader.ToAccount(doc), path, password, newLock);
        }
        catch (WrongPasswordException)
        {
            newLock.Dispose();
            StatusMessage = Loc.Tr("msg.wrongpassword");
        }
        catch (UnauthorizedAccessException)
        {
            newLock.Dispose();
            StatusMessage = Loc.Tr("msg.wallet.noaccess");
        }
        catch (Exception ex)
        {
            newLock.Dispose();
            StatusMessage = $"{Loc.Tr("msg.error")}: {ex.Message}";
        }
    }

    [RelayCommand]
    private void NewWallet()
    {
        if (IsWalletOpen)
            CloseWallet();
        _pendingOpenPath = null;
        StatusMessage = "";
    }

    private void OpenLoaded(
        WalletDocument doc, IWalletAccount account, string path, string? password, WalletLock walletLock)
    {
        _walletLock?.Dispose();
        _walletLock = walletLock;

        _doc = doc;
        _account = account;
        _walletPath = path;
        _password = password;
        MnemonicInput = ConfirmMnemonicInput = PassphraseInput = PasswordInput = ConfirmPasswordInput = WalletNameInput = "";
        ImportXkeyInput = ImportWifInput = ImportAddressInput = ImportXkeyDetectedKind = "";
        SetupStep = StepStart;
        OnPropertyChanged(nameof(IsWatchOnlyAccount));

        var walletKindTag = account switch
        {
            { IsWatchOnly: true } => " · watch-only",
            ImportedKeyAccount => " · imported",
            _ => ""
        };
        var pathTag = !string.IsNullOrEmpty(doc.AccountPath) ? $" · m/{doc.AccountPath}" : "";
        NetworkInfo = $"{doc.Network} · {doc.ScriptKind}{pathTag}{walletKindTag}";
        LoadContacts();
        ApplyCache(doc.Cache);
        IsWalletOpen = true;
        StatusMessage = Loc.Tr("msg.opened");
        _autoReconnect = true;  // keepalive retries the connection even if the first attempt fails
        _syncFailed = true;     // force the first automatic sync
        _ = ConnectAndSync();
    }
}