using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace AIRuntime.Editor
{
    public sealed partial class AIRuntimeKanbanSession
    {
        private static string VisualFileHash(string path)
        {
            string root = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
            string full = Path.GetFullPath(Path.Combine(root, path ?? string.Empty));
            if (!full.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) ||
                !File.Exists(full) || new FileInfo(full).Length > 64 * 1024 * 1024) return null;
            using (var stream = File.OpenRead(full))
            using (var hash = SHA256.Create())
                return string.Concat(hash.ComputeHash(stream).Select(value => value.ToString("x2")));
        }

        private string VisualWorkspaceFingerprint(KanbanTask task)
        {
            var paths = (task.ProducedArtifactPaths ?? new List<string>())
                .Concat(task.ConsumedArtifactPaths ?? new List<string>())
                .Concat(GetAuthoritativeDependencyTaskIds(task).Select(FindTask).Where(item => item != null)
                    .SelectMany(item => item.ProducedArtifactPaths ?? new List<string>()))
                .Append("ProjectSettings/TagManager.asset")
                .Where(path => !string.IsNullOrWhiteSpace(path)).Distinct(StringComparer.OrdinalIgnoreCase)
                .OrderBy(path => path, StringComparer.OrdinalIgnoreCase);
            var versions = new List<string> { task.Title, task.Description, task.AcceptanceCriteria };
            try
            {
                foreach (string path in paths)
                {
                    string hash = VisualFileHash(path);
                    // Directories are contracts too; snapshot their file contents.
                    string full = Path.GetFullPath(Path.Combine(Application.dataPath, "..", path));
                    if (hash == null && Directory.Exists(full))
                    {
                        string root = Path.GetFullPath(Path.Combine(Application.dataPath, "..")) + Path.DirectorySeparatorChar;
                        if (!full.StartsWith(root, StringComparison.OrdinalIgnoreCase)) return null;
                        string[] files = Directory.EnumerateFiles(full, "*", SearchOption.AllDirectories).Take(2001).ToArray();
                        if (files.Length > 2000) return null;
                        foreach (string file in files.OrderBy(value => value, StringComparer.OrdinalIgnoreCase))
                        {
                            string fileHash = VisualFileHash(file);
                            if (fileHash == null) return null;
                            versions.Add(file + ":" + fileHash);
                        }
                        continue;
                    }
                    if (hash == null) return null;
                    versions.Add(path + ":" + hash);
                }
            }
            catch (Exception error) when (error is IOException || error is UnauthorizedAccessException || error is ArgumentException)
            { return null; }
            return ComputeStableFingerprint(string.Join("\n", versions));
        }

        private static JArray HumanImages(KanbanTask task)
        {
            try { return JArray.Parse(task.HumanVisualArtifacts ?? "[]"); }
            catch (JsonException) { return new JArray(); }
        }

        private void RecordHumanVisualEvidence(KanbanTask task, string json)
        {
            task.HumanVisualReviewRequired = true;
            task.HumanVisualApprovalFingerprint = string.Empty;
            var images = HumanImages(task);
            foreach (JObject image in JArray.Parse(json).OfType<JObject>())
            {
                string path = image.Value<string>("Path");
                if (string.IsNullOrWhiteSpace(path)) continue;
                foreach (var old in images.OfType<JObject>().Where(item =>
                    string.Equals(item.Value<string>("Path"), path, StringComparison.OrdinalIgnoreCase)).ToArray()) old.Remove();
                var captured = (JObject)image.DeepClone();
                captured["WorkspaceFingerprint"] = VisualWorkspaceFingerprint(task);
                captured["CapturedAtUtc"] = DateTime.UtcNow.ToString("O");
                images.Add(captured);
            }
            while (images.Count > 24) images.RemoveAt(0);
            task.HumanVisualArtifacts = images.ToString(Formatting.None);
            TouchTask(task);
        }

        private string CurrentHumanVisualFingerprint(KanbanTask task)
        {
            string workspace = VisualWorkspaceFingerprint(task);
            var images = HumanImages(task);
            if (string.IsNullOrEmpty(workspace) || images.Count == 0) return null;
            try
            {
                foreach (JObject image in images.OfType<JObject>())
                    if (image.Value<string>("WorkspaceFingerprint") != workspace ||
                        string.IsNullOrEmpty(image.Value<string>("Sha256")) ||
                        !string.Equals(VisualFileHash(image.Value<string>("Path")), image.Value<string>("Sha256"), StringComparison.OrdinalIgnoreCase))
                        return null;
            }
            catch (Exception error) when (error is IOException || error is UnauthorizedAccessException || error is ArgumentException)
            { return null; }
            return ComputeStableFingerprint(workspace + "\n" + images.ToString(Formatting.None) + "\n" + task.ReviewResult + "\n" + task.ReviewEvidence);
        }

        private bool HasCurrentHumanVisualApproval(KanbanTask task) =>
            !string.IsNullOrEmpty(task.HumanVisualApprovalFingerprint) &&
            task.HumanVisualApprovalFingerprint == CurrentHumanVisualFingerprint(task);

        private bool TryAwaitHumanVisualReview(KanbanTask task)
        {
            if (!task.HumanVisualReviewRequired || HasCurrentHumanVisualApproval(task)) return false;
            task.Status = TaskStatus.Review;
            task.ReviewStatus = "Awaiting visual approval";
            task.ReviewFailureKind = string.Empty;
            task.AgentError = string.Empty;
            task.NextAttemptAtUtc = string.Empty;
            task.BlockedReason = "Automated review passed. A human must inspect the screenshots and approve visual appearance. Missing or changed evidence requires a fresh automated review.";
            task.HumanVisualReviewFingerprint = CurrentHumanVisualFingerprint(task) ?? string.Empty;
            TouchTask(task);
            MarkDirty();
            PersistSchedulerState();
            Repaint();
            return true;
        }

        private bool SubmitHumanVisualReview(KanbanTask task, bool approved, string notes)
        {
            if (task == null || task.ReviewStatus != "Awaiting visual approval" || IsTaskRunning(task) ||
                string.IsNullOrWhiteSpace(notes)) return false;
            if (approved)
            {
                if (EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling) return false;
                for (int i = 0; i < SceneManager.sceneCount; i++)
                    if (SceneManager.GetSceneAt(i).isDirty) return false;
                string current = CurrentHumanVisualFingerprint(task);
                if (string.IsNullOrEmpty(current) || current != task.HumanVisualReviewFingerprint) return false;
                task.HumanVisualApprovalFingerprint = current;
            }
            else
            {
                task.HumanVisualApprovalFingerprint = string.Empty;
                task.HumanVisualArtifacts = "[]";
            }
            task.HumanVisualReviewNotes = notes.Trim();
            task.HumanVisualReviewedAtUtc = DateTime.UtcNow.ToString("O");
            CompleteReviewTask(task, "Human visual reviewer", approved ? task.ReviewResult :
                "KANBAN_REVIEW: FAIL\n[ReviewFailureKind: ImplementationDefect]\nHuman visual review requested changes:\n" + notes.Trim(),
                string.Empty, task.ReviewEvidence);
            PersistSchedulerState();
            return true;
        }

        private void DrawHumanVisualReview(KanbanTask task)
        {
            if (!task.HumanVisualReviewRequired) return;
            EditorGUILayout.Space();
            EditorGUILayout.LabelField("Human visual review", EditorStyles.boldLabel);
            EditorGUILayout.HelpBox(task.ReviewStatus == "Awaiting visual approval"
                ? "Automated checks passed. Open the screenshots and check layout, readability, clipping, and appearance against this card's acceptance criteria. Approval covers only visual checks."
                : "Screenshots are reserved for human review. Automated checks must pass before you can approve.", MessageType.Info);
            foreach (JObject image in HumanImages(task).OfType<JObject>())
            {
                string path = image.Value<string>("Path");
                if (GUILayout.Button("Open screenshot: " + path))
                {
                    if (VisualFileHash(path) != null)
                        EditorUtility.OpenWithDefaultApp(Path.GetFullPath(Path.Combine(Application.dataPath, "..", path)));
                }
            }
            if (task.ReviewStatus != "Awaiting visual approval") return;
            string notes = EditorGUILayout.TextArea(task.HumanVisualReviewNotes ?? string.Empty, GUILayout.MinHeight(55));
            if (notes != task.HumanVisualReviewNotes) { task.HumanVisualReviewNotes = notes; MarkDirty(); }
            EditorGUILayout.LabelField("Describe what you checked, or what needs changing.", EditorStyles.wordWrappedMiniLabel);
            using (new EditorGUI.DisabledScope(string.IsNullOrWhiteSpace(notes) || IsTaskRunning(task)))
            {
                if (GUILayout.Button("Approve visual checks"))
                    if (!SubmitHumanVisualReview(task, true, notes))
                        EditorUtility.DisplayDialog("Evidence needs refreshing", "Save scene changes, exit Play Mode, then run a fresh review and capture current screenshots before approving.", "OK");
                if (GUILayout.Button("Request changes")) SubmitHumanVisualReview(task, false, notes);
            }
            if (GUILayout.Button("Refresh automated review and screenshots"))
            {
                task.HumanVisualApprovalFingerprint = string.Empty;
                task.HumanVisualArtifacts = "[]";
                RequeueReview(task);
                PersistSchedulerState();
            }
        }
    }
}
