using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using AIRuntime.Runtime.Capabilities;
using AIRuntime.Runtime.Capabilities.Attributes;
using AIRuntime.Runtime.Capabilities.Permissions;

using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;

using CapabilityRange =
    AIRuntime.Runtime.Capabilities.Attributes.RangeAttribute;


namespace AIRuntime.Editor
{
    [Serializable]
    internal sealed class EditorConsoleLogRecord
    {
        public long Sequence;

        public string TimestampUtc;

        public string Level;

        public string Message;

        public string StackTrace;
    }


    [InitializeOnLoad]
    internal static class EditorConsoleLogBuffer
    {
        private const int MaximumRecords =
            2000;

        private static readonly object Sync =
            new object();

        private static readonly List<EditorConsoleLogRecord> Records =
            new List<EditorConsoleLogRecord>();

        private static long _sequence;

        internal static readonly string CapturedSinceUtc;


        static EditorConsoleLogBuffer()
        {
            CapturedSinceUtc =
                DateTime.UtcNow.ToString(
                    "o");

            Application.logMessageReceivedThreaded +=
                Capture;
        }


        internal static List<EditorConsoleLogRecord> Snapshot()
        {
            lock (Sync)
            {
                return Records
                    .Select(
                        record =>
                            new EditorConsoleLogRecord
                            {
                                Sequence =
                                    record.Sequence,

                                TimestampUtc =
                                    record.TimestampUtc,

                                Level =
                                    record.Level,

                                Message =
                                    record.Message,

                                StackTrace =
                                    record.StackTrace
                            })
                    .ToList();
            }
        }


        private static void Capture(
            string condition,
            string stackTrace,
            LogType type)
        {
            lock (Sync)
            {
                Records.Add(
                    new EditorConsoleLogRecord
                    {
                        Sequence =
                            ++_sequence,

                        TimestampUtc =
                            DateTime.UtcNow.ToString(
                                "o"),

                        Level =
                            type.ToString(),

                        Message =
                            condition ??
                            string.Empty,

                        StackTrace =
                            stackTrace ??
                            string.Empty
                    });

                int excess =
                    Records.Count -
                    MaximumRecords;

                if (excess >
                    0)
                {
                    Records.RemoveRange(
                        0,
                        excess);
                }
            }
        }
    }


    [Serializable]
    public sealed class GetConsoleLogsArguments
    {
        [DefaultValue(true)]
        [Description("Include ordinary Log messages.")]
        public bool IncludeLogs { get; set; } =
            true;

        [DefaultValue(true)]
        [Description("Include Warning messages.")]
        public bool IncludeWarnings { get; set; } =
            true;

        [DefaultValue(true)]
        [Description("Include Error, Assert, and Exception messages.")]
        public bool IncludeErrors { get; set; } =
            true;

        [Description("Optional case-insensitive substring required in the message or stack trace.")]
        public string Contains { get; set; }

        [Description("Return only records whose sequence is greater than this value.")]
        public long AfterSequence { get; set; }

        [DefaultValue(false)]
        [Description("Include stack traces in returned records.")]
        public bool IncludeStackTrace { get; set; }

        [DefaultValue(200)]
        [CapabilityRange(1, 1000)]
        [Description("Maximum newest matching records to return.")]
        public int MaxEntries { get; set; } =
            200;
    }


    [Serializable]
    internal sealed class GetConsoleLogsResult
    {
        public string CapturedSinceUtc;

        public long LastSequence;

        public int EntryCount;

        public bool Truncated;

        public List<EditorConsoleLogRecord> Entries =
            new List<EditorConsoleLogRecord>();

        public string ResponseInstruction =
            "The buffer contains public Unity log events captured since CapturedSinceUtc, not historical Console entries from before the current domain load. Use LastSequence as afterSequence for incremental reads.";
    }


    public sealed class GetConsoleLogsCapability :
        CapabilityBase<GetConsoleLogsArguments>
    {
        public override string Name =>
            "get_console_logs";


        public override string Description =>
            "Returns a bounded, filterable stream of Unity Log, Warning, Error, Assert, and Exception messages captured during the current Editor domain.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


        protected override Task<object> ExecuteAsync(
            GetConsoleLogsArguments arguments,
            CapabilityContext context)
        {
            context.CancellationToken
                .ThrowIfCancellationRequested();

            GetConsoleLogsArguments request =
                arguments ??
                new GetConsoleLogsArguments();

            IEnumerable<EditorConsoleLogRecord> filtered =
                EditorConsoleLogBuffer
                    .Snapshot()
                    .Where(
                        record =>
                            record.Sequence >
                            request.AfterSequence)
                    .Where(
                        record =>
                            IncludeLevel(
                                record.Level,
                                request))
                    .Where(
                        record =>
                            string.IsNullOrWhiteSpace(
                                request.Contains) ||
                            record.Message.IndexOf(
                                request.Contains,
                                StringComparison.OrdinalIgnoreCase) >=
                                0 ||
                            record.StackTrace.IndexOf(
                                request.Contains,
                                StringComparison.OrdinalIgnoreCase) >=
                                0);

            List<EditorConsoleLogRecord> allMatches =
                filtered.ToList();

            List<EditorConsoleLogRecord> entries =
                allMatches
                    .Skip(
                        Math.Max(
                            0,
                            allMatches.Count -
                            request.MaxEntries))
                    .Select(
                        record =>
                        {
                            if (request.IncludeStackTrace)
                                return record;

                            record.StackTrace =
                                string.Empty;

                            return record;
                        })
                    .ToList();

            return Task.FromResult<object>(
                new GetConsoleLogsResult
                {
                    CapturedSinceUtc =
                        EditorConsoleLogBuffer
                            .CapturedSinceUtc,

                    LastSequence =
                        entries.Count >
                            0
                            ? entries[
                                    entries.Count -
                                    1]
                                .Sequence
                            : request.AfterSequence,

                    EntryCount =
                        entries.Count,

                    Truncated =
                        allMatches.Count >
                        entries.Count,

                    Entries =
                        entries
                });
        }


        private static bool IncludeLevel(
            string level,
            GetConsoleLogsArguments request)
        {
            if (string.Equals(
                    level,
                    LogType.Log.ToString(),
                    StringComparison.Ordinal))
            {
                return request.IncludeLogs;
            }

            if (string.Equals(
                    level,
                    LogType.Warning.ToString(),
                    StringComparison.Ordinal))
            {
                return request.IncludeWarnings;
            }

            return request.IncludeErrors;
        }
    }


    [Serializable]
    internal sealed class EditorCompilationMessageRecord
    {
        public string AssemblyPath;

        public string Type;

        public string Message;

        public string File;

        public int Line;

        public int Column;
    }


    [InitializeOnLoad]
    internal static class EditorCompilationMessageBuffer
    {
        [Serializable]
        private sealed class SavedCompilationSnapshot
        {
            public string CapturedSinceUtc;
            public List<EditorCompilationMessageRecord> Messages;
        }

        private static string SessionKey => "AIRuntime.CompilerDiagnostics." + Application.dataPath;
        private static bool _missingDiagnosticsRecaptureRequested;
        private static readonly object Sync =
            new object();

        private static readonly List<EditorCompilationMessageRecord> Records =
            new List<EditorCompilationMessageRecord>();

        internal static string CapturedSinceUtc
        {
            get;
            private set;
        }


        static EditorCompilationMessageBuffer()
        {
            CapturedSinceUtc =
                DateTime.UtcNow.ToString(
                    "o");

            RestoreSnapshot(SessionState.GetString(SessionKey, string.Empty));
            _missingDiagnosticsRecaptureRequested = SessionState.GetBool(SessionKey + ".Recapture", false);

            CompilationPipeline.compilationStarted +=
                OnCompilationStarted;

            CompilationPipeline.assemblyCompilationFinished +=
                OnAssemblyCompilationFinished;
        }


        internal static List<EditorCompilationMessageRecord> Snapshot()
        {
            lock (Sync)
            {
                return Records
                    .Select(
                        record =>
                            new EditorCompilationMessageRecord
                            {
                                AssemblyPath =
                                    record.AssemblyPath,

                                Type =
                                    record.Type,

                                Message =
                                    record.Message,

                                File =
                                    record.File,

                                Line =
                                    record.Line,

                                Column =
                                    record.Column
                            })
                    .ToList();
            }
        }


        private static void OnCompilationStarted(
            object context)
        {
            lock (Sync)
            {
                Records.Clear();

                CapturedSinceUtc =
                    DateTime.UtcNow.ToString(
                        "o");
                PersistSnapshot();
            }
        }


        private static void OnAssemblyCompilationFinished(
            string assemblyPath,
            CompilerMessage[] messages)
        {
            lock (Sync)
            {
                foreach (
                    CompilerMessage message
                    in messages ??
                       Array.Empty<CompilerMessage>())
                {
                    Records.Add(
                        new EditorCompilationMessageRecord
                        {
                            AssemblyPath =
                                EditorCapabilityUtility
                                    .NormalizePath(
                                        assemblyPath),

                            Type =
                                message.type.ToString(),

                            Message =
                                message.message ??
                                string.Empty,

                            File =
                                EditorCapabilityUtility
                                    .NormalizePath(
                                        message.file),

                            Line =
                                message.line,

                            Column =
                                message.column
                        });
                }
                PersistSnapshot();
            }
        }

        private static string SerializeSnapshot()
        {
            return JsonUtility.ToJson(new SavedCompilationSnapshot {
                CapturedSinceUtc = CapturedSinceUtc, Messages = Records.ToList() });
        }

        private static void PersistSnapshot() => SessionState.SetString(SessionKey, SerializeSnapshot());

        internal static bool TryRecaptureMissingDiagnostics(bool compilationFailed, bool isCompiling,
            int errorCount, Action requestCompilation)
        {
            if (!compilationFailed || errorCount > 0)
            {
                _missingDiagnosticsRecaptureRequested = false;
                SessionState.SetBool(SessionKey + ".Recapture", false);
                return false;
            }
            if (isCompiling || _missingDiagnosticsRecaptureRequested) return false;
            _missingDiagnosticsRecaptureRequested = true;
            SessionState.SetBool(SessionKey + ".Recapture", true);
            requestCompilation();
            return true;
        }

        private static void RestoreSnapshot(string json)
        {
            if (string.IsNullOrWhiteSpace(json)) return;
            try
            {
                var saved = JsonUtility.FromJson<SavedCompilationSnapshot>(json);
                if (saved?.Messages == null || string.IsNullOrWhiteSpace(saved.CapturedSinceUtc)) return;
                Records.Clear();
                Records.AddRange(saved.Messages.Where(message => message != null));
                CapturedSinceUtc = saved.CapturedSinceUtc;
            }
            catch (ArgumentException)
            {
                // Missing/corrupt evidence is reported separately from compiler success.
            }
        }
    }


    [Serializable]
    public sealed class GetCompilationErrorsArguments
    {
        [DefaultValue(false)]
        [Description("Include compiler warnings as well as errors.")]
        public bool IncludeWarnings { get; set; }

        [Description("Optional case-insensitive assembly-path substring.")]
        public string AssemblyContains { get; set; }

        [Description("Optional case-insensitive message or file substring.")]
        public string Contains { get; set; }

        [DefaultValue(200)]
        [CapabilityRange(1, 1000)]
        [Description("Maximum messages to return.")]
        public int MaxMessages { get; set; } =
            200;
    }


    [Serializable]
    internal sealed class GetCompilationErrorsResult
    {
        public string ErrorFingerprint;
        public bool CompilationFailed;
        public bool DiagnosticsUnavailable;
        public bool RecaptureRequested;
        public bool IsCompiling;

        public string CapturedSinceUtc;

        public int ErrorCount;

        public int WarningCount;

        public int MessageCount;

        public bool Truncated;

        public List<EditorCompilationMessageRecord> Messages =
            new List<EditorCompilationMessageRecord>();

        public string ResponseInstruction =
            "Messages are compiler diagnostics captured since CapturedSinceUtc and retained across assembly reload in this Editor session. IsCompiling=true means the snapshot may still be incomplete. " +
            "DiagnosticsUnavailable=true means Unity reports compilation failure but the errors were not captured. RecaptureRequested=true means AIRuntime requested compilation once to collect them; wait for that compilation, never infer success from an empty list. " +
            "When errors are present and compilation has stopped, inspect the exact failing file and referenced APIs before editing. " +
            "Kanban workers may read exact CompletedDependencyArtifactPaths from get_kanban_task_context to inspect reviewed upstream APIs; these paths grant no write permission. " +
            "Correct owned source and compile once. An unchanged ErrorFingerprint means polling has produced no new evidence, even with different filters. " +
            "If a required dependency is outside the declared graph, report [PlanningKickback] with the exact contract mismatch instead of guessing an API.";
    }


    public sealed class GetCompilationErrorsCapability :
        CapabilityBase<GetCompilationErrorsArguments>
    {
        public override string Name =>
            "get_compilation_errors";


        public override string Description =>
            "Returns Unity script compiler errors and optional warnings captured for the current compilation generation, including assembly, file, line, and column.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


        protected override Task<object> ExecuteAsync(
            GetCompilationErrorsArguments arguments,
            CapabilityContext context)
        {
            context.CancellationToken
                .ThrowIfCancellationRequested();

            GetCompilationErrorsArguments request =
                arguments ??
                new GetCompilationErrorsArguments();

            List<EditorCompilationMessageRecord> snapshot =
                EditorCompilationMessageBuffer
                    .Snapshot();

            int errorCount =
                snapshot.Count(
                    record =>
                        string.Equals(
                            record.Type,
                            CompilerMessageType.Error.ToString(),
                            StringComparison.Ordinal));

            int warningCount =
                snapshot.Count(
                    record =>
                        string.Equals(
                            record.Type,
                            CompilerMessageType.Warning.ToString(),
                            StringComparison.Ordinal));

            List<EditorCompilationMessageRecord> matches =
                snapshot
                    .Where(
                        record =>
                            request.IncludeWarnings ||
                            string.Equals(
                                record.Type,
                                CompilerMessageType.Error.ToString(),
                                StringComparison.Ordinal))
                    .Where(
                        record =>
                            string.IsNullOrWhiteSpace(
                                request.AssemblyContains) ||
                            record.AssemblyPath.IndexOf(
                                request.AssemblyContains,
                                StringComparison.OrdinalIgnoreCase) >=
                                0)
                    .Where(
                        record =>
                            string.IsNullOrWhiteSpace(
                                request.Contains) ||
                            record.Message.IndexOf(
                                request.Contains,
                                StringComparison.OrdinalIgnoreCase) >=
                                0 ||
                            record.File.IndexOf(
                                request.Contains,
                                StringComparison.OrdinalIgnoreCase) >=
                                0)
                    .ToList();

            List<EditorCompilationMessageRecord> returned =
                matches
                    .Take(
                        request.MaxMessages)
                    .ToList();

            bool recaptureRequested = EditorCompilationMessageBuffer.TryRecaptureMissingDiagnostics(
                EditorUtility.scriptCompilationFailed, EditorApplication.isCompiling, errorCount,
                () => CompilationPipeline.RequestScriptCompilation());

            return Task.FromResult<object>(
                new GetCompilationErrorsResult
                {
                    ErrorFingerprint = ComputeErrorFingerprint(snapshot),
                    CompilationFailed = EditorUtility.scriptCompilationFailed,
                    DiagnosticsUnavailable = EditorUtility.scriptCompilationFailed && errorCount == 0,
                    RecaptureRequested = recaptureRequested,
                    IsCompiling =
                        EditorApplication.isCompiling || recaptureRequested,

                    CapturedSinceUtc =
                        EditorCompilationMessageBuffer
                            .CapturedSinceUtc,

                    ErrorCount =
                        errorCount,

                    WarningCount =
                        warningCount,

                    MessageCount =
                        returned.Count,

                    Truncated =
                        matches.Count >
                        returned.Count,

                    Messages =
                        returned
                });
        }

        private static string ComputeErrorFingerprint(IEnumerable<EditorCompilationMessageRecord> messages)
        {
            string canonical = string.Join("\n", messages
                .Where(message => message.Type == CompilerMessageType.Error.ToString())
                .Select(message => string.Join("|", message.AssemblyPath, message.File,
                    message.Line, message.Column, message.Message))
                .OrderBy(message => message, StringComparer.Ordinal));
            using (var hash = System.Security.Cryptography.SHA256.Create())
                return Convert.ToBase64String(hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(canonical)));
        }
    }
}
