using Avalonia.Threading;
using System;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.Services;
using ClaudeDo.Ui.ViewModels.Islands;
using ClaudeDo.Ui.ViewModels.Modals;
using Microsoft.EntityFrameworkCore;

namespace ClaudeDo.Ui.ViewModels;

public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
{
    public ListsIslandViewModel? Lists { get; }
    public TasksIslandViewModel? Tasks { get; }
    public DetailsIslandViewModel? Details { get; }
    public IWorkerClient? Worker { get; }
    public UpdateCheckService UpdateCheck => _updateCheck;

    public string ConnectionText =>
        Worker?.IsConnected == true ? Loc.T("vm.connection.online")
        : Worker?.IsReconnecting == true ? Loc.T("vm.connection.connecting")
        : Loc.T("vm.connection.offline");

    public bool IsOffline => Worker?.IsConnected != true && Worker?.IsReconnecting != true;

    private readonly UpdateCheckService _updateCheck = null!;
    private readonly InstallerLocator _installerLocator = null!;
    private readonly WorkerLocator _workerLocator = null!;
    private readonly IDbContextFactory<ClaudeDoDbContext>? _dbFactory;
    private readonly Func<WorktreesOverviewModalViewModel> _worktreesOverviewVmFactory = () => null!;
    private readonly Func<WeeklyReportModalViewModel> _weeklyReportVmFactory = () => null!;
    private readonly Func<MergeModalViewModel> _mergeVmFactory = () => null!;
    private readonly Func<RepoImportModalViewModel>? _repoImportVmFactory;

    public Func<MergeModalViewModel> ResolveMergeVm => _mergeVmFactory;

    // Layer C seam: composition root sets the factory; the dialog service shows the resolver.
    public Func<string, ClaudeDo.Ui.ViewModels.Conflicts.ConflictResolverViewModel>? ConflictResolverFactory { get; set; }

    // Single dialog seam (set by MainWindow); propagated to the lists island.
    private IDialogService? _dialogs;
    public IDialogService? Dialogs
    {
        get => _dialogs;
        set
        {
            _dialogs = value;
            if (Lists is not null) Lists.Dialogs = value;
        }
    }

    public async Task RequestConflictResolutionAsync(string taskId, string targetBranch)
    {
        if (ConflictResolverFactory is null || Dialogs is null) return;
        var vm = ConflictResolverFactory(taskId);
        var hasConflicts = await vm.OpenAsync(targetBranch);
        if (hasConflicts)
            await Dialogs.ShowConflictResolverAsync(vm);
    }

    [ObservableProperty] private bool _isUpdateBannerVisible;
    [ObservableProperty] private string? _updateBannerLatestVersion;
    [ObservableProperty] private string? _inlineUpdateStatus;
    private bool _bannerDismissedThisSession;

    [ObservableProperty]
    private double _windowWidth = 1280;

    [ObservableProperty]
    private string? _workerLogText;

    [ObservableProperty]
    private WorkerLogLevel _workerLogLevel;

    [ObservableProperty]
    private bool _isWorkerLogVisible;

    public bool ShowDetails => WindowWidth >= 1100;
    public bool ShowLists => WindowWidth >= 780;

    private readonly System.Timers.Timer _clearTimer = new(30_000) { AutoReset = false };
    private readonly System.Timers.Timer _connectTimer = new(12_000) { AutoReset = false };

    [ObservableProperty] private string? _primeStatus;
    private readonly System.Timers.Timer _primeStatusTimer = new(5_000) { AutoReset = false };

    [RelayCommand]
    private void FocusSearch() => Lists?.RequestFocusSearch();

    [RelayCommand]
    private void FocusAddTask() => Tasks?.RequestFocusAddTask();

    public async Task ToggleSelectedDoneAsync()
    {
        if (Tasks?.SelectedTask is { } row)
            await Tasks.ToggleDoneCommand.ExecuteAsync(row);
    }

    partial void OnWindowWidthChanged(double value)
    {
        OnPropertyChanged(nameof(ShowDetails));
        OnPropertyChanged(nameof(ShowLists));
    }

    public void OnWorkerLogReceived(WorkerLogEntry entry)
    {
        var hhmm = entry.TimestampUtc.ToLocalTime().ToString("HH:mm");
        WorkerLogText = $"{hhmm} · {entry.Message}";
        WorkerLogLevel = entry.Level;
        IsWorkerLogVisible = true;
        _clearTimer.Stop();
        _clearTimer.Start();
    }

    public void ClearWorkerLog()
    {
        IsWorkerLogVisible = false;
        WorkerLogText = null;
    }

    // Surfaces a UI-originated failure in the footer status strip (same line as the
    // worker log), color-coded as an error and auto-cleared by _clearTimer.
    public void FlashFooterError(string message)
    {
        WorkerLogText = $"{DateTime.Now:HH:mm} · {message}";
        WorkerLogLevel = WorkerLogLevel.Error;
        IsWorkerLogVisible = true;
        _clearTimer.Stop();
        _clearTimer.Start();
    }

    private void OnPrimeFired(PrimeFiredEvent evt)
    {
        var when = evt.FiredAt.LocalDateTime.ToString("HH:mm");
        PrimeStatus = evt.Success
            ? $"✓ Primed Claude at {when}"
            : $"⚠ Prime failed: {evt.Message}";
        _primeStatusTimer.Stop();
        _primeStatusTimer.Start();
    }

    private void OnPlanningMergeConflict(string planningTaskId, string subtaskId, IReadOnlyList<string> conflictedFiles)
    {
        // Already on UI thread (WorkerClient dispatches via Dispatcher.UIThread.Post).
        // A unit-merge conflict resolves in the same in-app 3-way editor as a single-task merge.
        _ = OpenPlanningConflictAsync(planningTaskId, subtaskId);
    }

    private async Task OpenPlanningConflictAsync(string planningTaskId, string subtaskId)
    {
        if (ConflictResolverFactory is null || Dialogs is null) return;
        var vm = ConflictResolverFactory(subtaskId);
        var hasConflicts = await vm.OpenForPlanningAsync(planningTaskId, subtaskId);
        if (hasConflicts)
            await Dialogs.ShowConflictResolverAsync(vm);
    }

    // For tests only — does NOT wire up events.
    internal IslandsShellViewModel() { }

    public IslandsShellViewModel(
        ListsIslandViewModel lists,
        TasksIslandViewModel tasks,
        DetailsIslandViewModel details,
        IWorkerClient worker,
        UpdateCheckService updateCheck,
        InstallerLocator installerLocator,
        WorkerLocator workerLocator,
        IDbContextFactory<ClaudeDoDbContext> dbFactory,
        Func<WorktreesOverviewModalViewModel> worktreesOverviewVmFactory,
        Func<WeeklyReportModalViewModel> weeklyReportVmFactory,
        Func<MergeModalViewModel> mergeVmFactory,
        Func<RepoImportModalViewModel> repoImportVmFactory)
    {
        Lists = lists; Tasks = tasks; Details = details; Worker = worker;
        _updateCheck = updateCheck;
        _installerLocator = installerLocator;
        _workerLocator = workerLocator;
        _dbFactory = dbFactory;
        _worktreesOverviewVmFactory = worktreesOverviewVmFactory;
        _weeklyReportVmFactory = weeklyReportVmFactory;
        _mergeVmFactory = mergeVmFactory;
        _repoImportVmFactory = repoImportVmFactory;
        Lists.SelectionChanged += (_, _) => Tasks.LoadForList(Lists.SelectedList);
        Tasks.SelectionChanged += (_, _) => Details.Bind(Tasks.SelectedTask);
        Tasks.NotesRequested += () => Details.ShowNotes();
        Tasks.PrepRequested += () => Details.ShowPrep();
        Tasks.ErrorReported += FlashFooterError;
        Tasks.TasksChanged     += (_, _) => _ = Lists.RefreshCountsAsync();
        Tasks.OpenListSettingsRequested += (_, _) =>
        {
            if (Lists.SelectedList is { } row)
                Lists.OpenListSettingsCommand.Execute(row);
        };
        Details.CloseDetail = () => Tasks.SelectedTask = null;
        Details.DeleteFromList = row =>
        {
            Tasks.LoadForList(Lists.SelectedList);
            _ = Lists.RefreshCountsAsync();
            return System.Threading.Tasks.Task.CompletedTask;
        };
        Worker.PropertyChanged += (_, e) =>
        {
            if (e.PropertyName is nameof(IWorkerClient.IsConnected) or nameof(IWorkerClient.IsReconnecting))
            {
                OnPropertyChanged(nameof(ConnectionText));
                OnPropertyChanged(nameof(IsOffline));
            }
        };
        Worker.WorkerLogReceivedEvent += OnWorkerLogReceived;
        Worker.PlanningMergeConflictEvent += OnPlanningMergeConflict;
        Worker.PrimeFired += OnPrimeFired;
        _clearTimer.Elapsed += (_, _) =>
        {
            if (Dispatcher.UIThread.CheckAccess())
                ClearWorkerLog();
            else
                Dispatcher.UIThread.Post(ClearWorkerLog);
        };
        _primeStatusTimer.Elapsed += (_, _) =>
            Avalonia.Threading.Dispatcher.UIThread.Post(() => PrimeStatus = null);
        _connectTimer.Elapsed += (_, _) => Dispatcher.UIThread.Post(() =>
        {
            if (DecideShowConnectionPrompt(IsOffline)) _ = OpenWorkerConnectionHelpAsync();
        });
        _connectTimer.Start();
        _ = Lists.LoadAsync();
        _updateCheck.PropertyChanged += (_, e) =>
        {
            if (e.PropertyName == nameof(UpdateCheckService.LastCheckStatus))
            {
                RefreshBannerFromStatus();
            }
        };
        // Fire-and-forget startup check — never block UI.
        _ = Task.Run(async () =>
        {
            try { await _updateCheck.CheckNowAsync(CancellationToken.None); } catch { }
        });
    }

    public void Dispose()
    {
        _clearTimer.Stop();
        _clearTimer.Dispose();
        _connectTimer.Stop();
        _connectTimer.Dispose();
        _primeStatusTimer.Stop();
        _primeStatusTimer.Dispose();
    }

    private void RefreshBannerFromStatus()
    {
        switch (_updateCheck.LastCheckStatus)
        {
            case UpdateCheckStatus.UpdateAvailable:
                if (_bannerDismissedThisSession) { IsUpdateBannerVisible = false; break; }
                UpdateBannerLatestVersion = _updateCheck.LatestVersion;
                IsUpdateBannerVisible = true;
                InlineUpdateStatus = null;
                break;
            case UpdateCheckStatus.UpToDate:
                IsUpdateBannerVisible = false;
                ShowInlineStatus($"You're up to date (v{_updateCheck.CurrentVersion})");
                break;
            case UpdateCheckStatus.CheckFailed:
                ShowInlineStatus("Could not check for updates");
                break;
        }
    }

    private async void ShowInlineStatus(string text)
    {
        InlineUpdateStatus = text;
        await Task.Delay(3000);
        if (InlineUpdateStatus == text) InlineUpdateStatus = null;
    }

    [RelayCommand]
    private async Task OpenAbout()
    {
        var vm = new AboutModalViewModel();
        if (Dialogs is not null) await Dialogs.ShowAboutAsync(vm);
    }

    [RelayCommand]
    private async Task OpenLogVisualizer()
    {
        if (Dialogs is null || Worker is null) return;
        var vm = new LogVisualizerViewModel(Worker);
        await vm.RefreshAsync();
        await Dialogs.ShowLogVisualizerAsync(vm);
    }

    private bool _connectionPromptShown;

    internal bool DecideShowConnectionPrompt(bool isOffline)
    {
        if (!isOffline) return false;
        if (_connectionPromptShown) return false;
        _connectionPromptShown = true;
        return true;
    }

    private async Task OpenWorkerConnectionHelpAsync()
    {
        var vm = new WorkerConnectionModalViewModel(_workerLocator, _installerLocator);
        if (Dialogs is not null) await Dialogs.ShowWorkerConnectionAsync(vm);
    }

    [RelayCommand]
    private Task OpenWorkerConnectionHelp() => OpenWorkerConnectionHelpAsync();

    [RelayCommand]
    private async Task OpenRepoImport()
    {
        if (Dialogs is null || _repoImportVmFactory is null) return;
        var vm = _repoImportVmFactory();
        await vm.LoadAsync();
        await Dialogs.ShowRepoImportAsync(vm);
        if (Lists is not null) await Lists.LoadAsync();
    }

    private bool _worktreesOverviewOpen;

    [RelayCommand]
    private async Task OpenWorktreesOverviewGlobalAsync()
    {
        if (Dialogs is null || _worktreesOverviewOpen) return;
        _worktreesOverviewOpen = true;
        try
        {
            var vm = _worktreesOverviewVmFactory();
            vm.Configure(null, null);
            await vm.LoadAsync();
            await Dialogs.ShowWorktreesOverviewAsync(vm);
        }
        finally { _worktreesOverviewOpen = false; }
    }

    private bool _weeklyReportOpen;

    [RelayCommand]
    private async Task OpenWeeklyReport()
    {
        if (Dialogs is null || _weeklyReportOpen) return;
        _weeklyReportOpen = true;
        try
        {
            var vm = _weeklyReportVmFactory();
            await vm.InitializeAsync();
            await Dialogs.ShowWeeklyReportAsync(vm);
        }
        finally { _weeklyReportOpen = false; }
    }

    [RelayCommand]
    private async Task CheckForUpdatesAsync()
    {
        await _updateCheck.CheckNowAsync(CancellationToken.None);
    }

    [ObservableProperty] private string? _restartWorkerStatus;

    [RelayCommand]
    private async Task RestartWorkerAsync()
    {
        RestartWorkerStatus = Loc.T("vm.shell.restartingWorker");
        try
        {
            await Task.Run(RestartWorkerService);
            await FlashRestartStatusAsync("Worker restarted.");
        }
        catch (Exception ex)
        {
            await FlashRestartStatusAsync($"Restart failed: {ex.Message}");
        }
    }

    private void RestartWorkerService()
    {
        var exe = _workerLocator.Find();
        if (exe is null) throw new InvalidOperationException("Worker executable not found.");

        // Only kill the worker belonging to THIS installation — not any other
        // ClaudeDo.Worker on the machine (e.g. a second install).
        var exeFull = System.IO.Path.GetFullPath(exe);
        foreach (var p in System.Diagnostics.Process.GetProcessesByName("ClaudeDo.Worker"))
        {
            try
            {
                var path = p.MainModule?.FileName;
                if (path is not null &&
                    !string.Equals(System.IO.Path.GetFullPath(path), exeFull, StringComparison.OrdinalIgnoreCase))
                    continue;
                p.Kill(entireProcessTree: true);
                p.WaitForExit(10000);
            }
            catch { /* may have exited or be inaccessible */ }
            finally { p.Dispose(); }
        }
        System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(exe) { UseShellExecute = true });
    }

    private async Task FlashRestartStatusAsync(string text)
    {
        RestartWorkerStatus = text;
        await Task.Delay(3000);
        if (RestartWorkerStatus == text) RestartWorkerStatus = null;
    }

    [RelayCommand]
    private void DismissBanner()
    {
        _bannerDismissedThisSession = true;
        IsUpdateBannerVisible = false;
    }

    [RelayCommand]
    private void UpdateNow()
    {
        var path = _installerLocator.Find();
        if (path is null) return;

        try
        {
            System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true });
            Environment.Exit(0);
        }
        catch
        {
            // Intentionally silent — if this fails there's nothing useful to show.
        }
    }
}
