// Copyright (c) 2025 TerraByte Inc.
//
// This script acts as the Controller for the ArbitratorWindow. It manages all
// state and business logic, separating it from the UI rendering code in the window.

using System;
using GitMerge;
using System.Linq;
using UnityEditor;
using Terra.Arbitrator.Settings;
using Terra.Arbitrator.Services;
using Terra.Arbitrator.Promises;
using System.Collections.Generic;

namespace Terra.Arbitrator.GUI
{
    public class ArbitratorController
    {
        private List<GitChange> _changes = new();
        public IReadOnlyList<GitChange> Changes => _changes;
        public string InfoMessage { get; private set; }
        public string ErrorMessage { get; private set; }
        public bool IsLoading { get; private set; }
        public string LoadingMessage { get; private set; } = "";

        private readonly Action _requestRepaint;
        private readonly Func<string, string, bool> _displayDialog;

        /// <summary>
        /// Initializes the controller.
        /// </summary>
        /// <param name="requestRepaint">A callback to the window's Repaint() method.</param>
        /// <param name="displayDialog">A callback to EditorUtility.DisplayDialog for user confirmations.</param>
        public ArbitratorController(Action requestRepaint, Func<string, string, bool> displayDialog)
        {
            _requestRepaint = requestRepaint;
            _displayDialog = displayDialog;
        }
        
        public void OnEnable()
        {
            if (SessionState.GetBool(BetterGitStatePersistence.ResetQueueKey, false))
            {
                SessionState.EraseString(BetterGitStatePersistence.ResetQueueKey);
                InfoMessage = "Multi-file reset complete. Pulling again to confirm...";
                Pull();
            }
            else
            {
                Refresh();
            }
        }

        public void Refresh()
        {
            StartOperation("Refreshing status...");
            
            UnstageStep()
                .Then(CompareStep)
                .Then(changes => _changes = changes)
                .Catch(HandleOperationError)
                .Finally(FinishOperation);
        }
        
        public void Pull()
        {
            if (IsLoading) return;
            StartOperation("Analyzing for conflicts...");

            GitService.AnalyzePullConflicts()
                .Then(analysisResult =>
                {
                    if (analysisResult.HasConflicts)
                    {
                        FinishOperation();
                        ConflictResolutionWindow.ShowWindow(analysisResult.ConflictingFiles, filesToActOn =>
                        {
                            if (filesToActOn == null) return; 
                            if (filesToActOn.Any()) ResetMultipleFiles(filesToActOn);
                            else ForcePull(); 
                        });
                    }
                    else
                    {
                        StartOperation("No conflicts found. Pulling changes...");
                        GitService.PerformSafePull()
                            .Then(successMessage =>
                            {
                                InfoMessage = successMessage;
                                // Refresh will handle its own start/finish states
                                Refresh();
                            })
                            .Catch(ex => {
                                // If the pull fails, we need to handle the error and finish
                                HandleOperationError(ex);
                                FinishOperation();
                            });
                    }
                })
                .Catch(ex => {
                    HandleOperationError(ex);
                    FinishOperation();
                });
        }

        public void CommitAndPush(string commitMessage)
        {   
            var selectedFiles = _changes.Where(c => c.IsSelectedForCommit).ToList();
            
            var username = BetterGitSettings.Username;
            var email = BetterGitSettings.Email;
            
            StartOperation("Staging, committing, and pushing files...");

            GitService.CommitAndPush(selectedFiles, commitMessage, username, email)
                .Then(successMessage => {
                    InfoMessage = successMessage;
                    Refresh();
                })
                .Catch(ex => {
                    HandleOperationError(ex);
                    FinishOperation();
                });
        }

        public void ResetFile(GitChange change)
        {
            if (IsLoading) return;
            var userConfirmed = _displayDialog("Confirm Reset", $"Are you sure you want to revert all local changes to '{change.FilePath}'? This action cannot be undone.");
            if (!userConfirmed) return;
            
            StartOperation($"Resetting {change.FilePath}...");
            
            GitService.ResetFileChanges(change)
                .Then(successMessage => {
                    InfoMessage = successMessage;
                    Refresh();
                })
                .Catch(ex => {
                    HandleOperationError(ex);
                    FinishOperation();
                });
        }
        
        public void DiffFile(GitChange change)
        {
            if (IsLoading) return;
            StartOperation($"Launching diff for {change.FilePath}...");
            
            GitService.LaunchExternalDiff(change)
                .Catch(HandleOperationError)
                .Finally(Refresh);
        }
        
        public void ResolveConflict(GitChange change)
        {
            if (IsLoading) return;
            
            StartOperation($"Opening merge tool for {change.FilePath}...");
            
            var fileExtension = System.IO.Path.GetExtension(change.FilePath).ToLower();
            if (fileExtension is ".prefab" or ".unity")
            {
                try
                {
                    GitMergeWindow.ResolveConflict(change.FilePath);
                }
                catch (Exception e)
                {
                    ErrorMessage = e.Message;
                }
                
                Refresh();
                return;
            }
            
            GitService.LaunchMergeTool(change)
                .Then(successMessage => { InfoMessage = successMessage; })
                .Catch(HandleOperationError)
                .Finally(Refresh);
        }

        public void ResetAll()
        {
            var userConfirmed = _displayDialog(
                "Confirm Reset All",
                "Are you sure you want to discard ALL local changes (modified, added, and untracked files)?\n\nThis action cannot be undone.");

            if (!userConfirmed) return;

            StartOperation("Discarding all local changes...");

            GitService.ResetAllChanges()
                .Then(successMessage =>
                {
                    InfoMessage = successMessage;
                })
                .Catch(HandleOperationError)
                .Finally(Refresh);
        }

        public void SetAllSelection(bool selected)
        {
            if (_changes == null) return;
            foreach (var change in _changes)
            {
                if (change.Status != LibGit2Sharp.ChangeKind.Conflicted)
                {
                   change.IsSelectedForCommit = selected;
                }
            }
        }

        // --- Private Methods ---
        
        private static IPromise<bool> UnstageStep()
        {
            return GitService.UnstageAllFilesIfSafe();
        }

        private IPromise<List<GitChange>> CompareStep(bool wasUnstaged)
        {
            if (wasUnstaged)
            {
                InfoMessage = "Found and unstaged files for review.";
            }
            return GitService.CompareLocalToRemote();
        }
        
        // --- Shared Helper Methods ---

        private void StartOperation(string loadingMessage)
        {
            IsLoading = true;
            LoadingMessage = loadingMessage;
            ClearMessages();
            _changes = null;
            _requestRepaint?.Invoke();
        }

        private void HandleOperationError(Exception ex)
        {
            ErrorMessage = $"Operation Failed: {ex.Message}";
        }
        
        private void FinishOperation()
        {
            IsLoading = false;
            _requestRepaint?.Invoke();
        }
        
        private void ClearMessages()
        {
            ErrorMessage = null;
            InfoMessage = null;
        }

        private void ResetMultipleFiles(List<string> filePaths)
        {
            InfoMessage = $"Starting reset for {filePaths.Count} file(s)... This may trigger script compilation.";
            _requestRepaint?.Invoke();
            SessionState.SetString("BetterGit.ResetQueue", string.Join(";", filePaths));
            EditorApplication.delayCall += BetterGitStatePersistence.ContinueInterruptedReset;
        }
        
        private void ForcePull()
        {
            StartOperation("Attempting to pull and create conflicts...");
            
            EditorApplication.LockReloadAssemblies();
            GitService.ForcePull()
                .Then(_ =>
                {
                    InfoMessage = "Pull resulted in conflicts. Please resolve them below.";
                })
                .Catch(HandleOperationError)
                .Finally(() =>
                {
                    EditorApplication.UnlockReloadAssemblies();
                    AssetDatabase.Refresh();
                    Refresh();
                });
        }
    }
}