using System.IO;
using System.Text.Json;
using AxCopilot.Models;

namespace AxCopilot.Services;

/// <summary>
/// 대화 내역을 로컬에 AES-256-GCM 암호화하여 저장/로드합니다.
/// 파일 형식: conversations/{id}.axchat (암호화 바이너리)
/// 스레드 안전: ReaderWriterLockSlim으로 동시 접근 보호.
/// 원자적 쓰기: 임시 파일 → rename 패턴으로 크래시 시 데이터 손실 방지.
/// </summary>
public class ChatStorageService
{
    private static readonly string ConversationsDir =
        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                     "AxCopilot", "conversations");

    private static readonly JsonSerializerOptions JsonOpts = new()
    {
        WriteIndented = false,
        PropertyNameCaseInsensitive = true
    };

    private static readonly ReaderWriterLockSlim Lock = new();

    public ChatStorageService()
    {
        Directory.CreateDirectory(ConversationsDir);
    }

    /// <summary>대화를 암호화하여 저장합니다 (원자적 쓰기).</summary>
    public void Save(ChatConversation conversation)
    {
        conversation.UpdatedAt = DateTime.Now;
        // 검색용 미리보기 자동 갱신 (첫 사용자 메시지 100자)
        if (string.IsNullOrEmpty(conversation.Preview) && conversation.Messages.Count > 0)
        {
            var firstUser = conversation.Messages.FirstOrDefault(m => m.Role == "user");
            if (firstUser != null)
                conversation.Preview = firstUser.Content.Length > 100
                    ? firstUser.Content[..100] : firstUser.Content;
        }
        var json = JsonSerializer.Serialize(conversation, JsonOpts);
        var path = GetFilePath(conversation.Id);
        var tempPath = path + ".tmp";

        Lock.EnterWriteLock();
        try
        {
            CryptoService.EncryptToFile(tempPath, json);
            // 원자적 교체: 기존 파일이 있으면 덮어쓰기
            if (File.Exists(path)) File.Delete(path);
            File.Move(tempPath, path);
            UpdateMetaCache(conversation);
        }
        catch (Exception ex)
        {
            // 임시 파일 정리
            try { if (File.Exists(tempPath)) File.Delete(tempPath); } catch (Exception) { }
            LogService.Warn($"대화 저장 실패 ({conversation.Id}): {ex.Message}");
            throw;
        }
        finally
        {
            Lock.ExitWriteLock();
        }
    }

    /// <summary>대화를 복호화하여 로드합니다.</summary>
    public ChatConversation? Load(string id)
    {
        var path = GetFilePath(id);
        Lock.EnterReadLock();
        try
        {
            if (!File.Exists(path)) return null;
            var json = CryptoService.DecryptFromFile(path);
            return JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
        }
        catch (Exception ex)
        {
            LogService.Warn($"대화 로드 실패 ({id}): {ex.Message}");
            return null;
        }
        finally
        {
            Lock.ExitReadLock();
        }
    }

    // ── 메타 캐시 ─────────────────────────────────────────────────────────
    private List<ChatConversation>? _metaCache;
    private bool _metaDirty = true;

    /// <summary>메타 캐시를 무효화합니다. 다음 LoadAllMeta 호출 시 디스크에서 다시 읽습니다.</summary>
    public void InvalidateMetaCache() => _metaDirty = true;

    /// <summary>메타 캐시에서 특정 항목만 업데이트합니다 (전체 재로드 없이).</summary>
    public void UpdateMetaCache(ChatConversation conv)
    {
        if (_metaCache == null) return;
        var existing = _metaCache.FindIndex(c => c.Id == conv.Id);
        var meta = new ChatConversation
        {
            Id = conv.Id, Title = conv.Title,
            CreatedAt = conv.CreatedAt, UpdatedAt = conv.UpdatedAt,
            Pinned = conv.Pinned, Category = conv.Category,
            Tab = conv.Tab, SystemCommand = conv.SystemCommand,
            WorkFolder = conv.WorkFolder, Preview = conv.Preview,
            Messages = new()
        };
        if (existing >= 0)
            _metaCache[existing] = meta;
        else
            _metaCache.Add(meta);
    }

    /// <summary>메타 캐시에서 항목을 제거합니다.</summary>
    public void RemoveFromMetaCache(string id)
    {
        _metaCache?.RemoveAll(c => c.Id == id);
    }

    /// <summary>모든 대화의 메타 정보(메시지 미포함)를 로드합니다. 캐시를 사용합니다.</summary>
    public List<ChatConversation> LoadAllMeta()
    {
        if (!_metaDirty && _metaCache != null)
        {
            return _metaCache.OrderByDescending(c => c.Pinned)
                             .ThenByDescending(c => c.UpdatedAt)
                             .ToList();
        }

        var result = new List<ChatConversation>();
        if (!Directory.Exists(ConversationsDir))
        {
            _metaCache = result;
            _metaDirty = false;
            return result;
        }

        Lock.EnterReadLock();
        try
        {
            foreach (var file in Directory.GetFiles(ConversationsDir, "*.axchat"))
            {
                try
                {
                    var json = CryptoService.DecryptFromFile(file);
                    var conv = JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
                    if (conv != null)
                    {
                        var meta = new ChatConversation
                        {
                            Id = conv.Id,
                            Title = conv.Title,
                            CreatedAt = conv.CreatedAt,
                            UpdatedAt = conv.UpdatedAt,
                            Pinned = conv.Pinned,
                            Category = conv.Category,
                            Tab = conv.Tab,
                            SystemCommand = conv.SystemCommand,
                            WorkFolder = conv.WorkFolder,
                            Preview = conv.Preview,
                            Messages = new()
                        };
                        result.Add(meta);
                    }
                }
                catch (Exception ex)
                {
                    LogService.Warn($"대화 메타 로드 실패 ({Path.GetFileName(file)}): {ex.Message}");
                }
            }
        }
        finally
        {
            Lock.ExitReadLock();
        }

        _metaCache = result;
        _metaDirty = false;

        return result.OrderByDescending(c => c.Pinned)
                     .ThenByDescending(c => c.UpdatedAt)
                     .ToList();
    }

    /// <summary>특정 대화를 삭제합니다.</summary>
    public void Delete(string id)
    {
        var path = GetFilePath(id);
        Lock.EnterWriteLock();
        try
        {
            if (File.Exists(path)) File.Delete(path);
            RemoveFromMetaCache(id);
        }
        catch (Exception ex)
        {
            LogService.Warn($"대화 삭제 실패 ({id}): {ex.Message}");
        }
        finally
        {
            Lock.ExitWriteLock();
        }
    }

    /// <summary>모든 대화를 삭제합니다.</summary>
    public int DeleteAll()
    {
        if (!Directory.Exists(ConversationsDir)) return 0;
        Lock.EnterWriteLock();
        try
        {
            var files = Directory.GetFiles(ConversationsDir, "*.axchat");
            int count = 0;
            foreach (var f in files)
            {
                try { File.Delete(f); count++; }
                catch (Exception ex) { LogService.Warn($"파일 삭제 실패 ({Path.GetFileName(f)}): {ex.Message}"); }
            }
            InvalidateMetaCache();
            return count;
        }
        finally
        {
            Lock.ExitWriteLock();
        }
    }

    /// <summary>보관 기간을 초과한 대화를 삭제합니다 (핀 고정 제외).</summary>
    public int PurgeExpired(int retentionDays)
    {
        if (retentionDays <= 0) return 0;
        var cutoff = DateTime.Now.AddDays(-retentionDays);
        int count = 0;

        Lock.EnterWriteLock();
        try
        {
            foreach (var file in Directory.GetFiles(ConversationsDir, "*.axchat"))
            {
                try
                {
                    var json = CryptoService.DecryptFromFile(file);
                    var conv = JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
                    if (conv != null && !conv.Pinned && conv.UpdatedAt < cutoff)
                    {
                        File.Delete(file);
                        RemoveFromMetaCache(conv.Id);
                        count++;
                    }
                }
                catch (Exception ex)
                {
                    LogService.Warn($"만료 대화 정리 실패 ({Path.GetFileName(file)}): {ex.Message}");
                }
            }
        }
        finally
        {
            Lock.ExitWriteLock();
        }
        return count;
    }

    /// <summary>드라이브 사용률이 98% 이상이면 오래된 대화부터 삭제합니다 (핀 고정 제외).</summary>
    public int PurgeForDiskSpace(double threshold = 0.98)
    {
        try
        {
            var drive = new DriveInfo(Path.GetPathRoot(ConversationsDir) ?? "C");
            if (drive.TotalSize <= 0) return 0;
            var usageRatio = 1.0 - (double)drive.AvailableFreeSpace / drive.TotalSize;
            if (usageRatio < threshold) return 0;

            LogService.Info($"드라이브 사용률 {usageRatio:P1} — 대화 정리 시작 (임계값: {threshold:P0})");

            // 오래된 순으로 정렬하여 삭제 (핀 고정 제외)
            var files = Directory.GetFiles(ConversationsDir, "*.axchat")
                .Select(f => new { Path = f, LastWrite = File.GetLastWriteTime(f) })
                .OrderBy(f => f.LastWrite)
                .ToList();

            int count = 0;
            Lock.EnterWriteLock();
            try
            {
                foreach (var file in files)
                {
                    // 사용률이 임계값 미만으로 내려가면 중단
                    var currentUsage = 1.0 - (double)drive.AvailableFreeSpace / drive.TotalSize;
                    if (currentUsage < threshold - 0.02) break; // 2% 여유 확보 후 중단

                    try
                    {
                        var json = CryptoService.DecryptFromFile(file.Path);
                        var conv = JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
                        if (conv != null && conv.Pinned) continue; // 핀 고정 건너뜀

                        File.Delete(file.Path);
                        count++;
                    }
                    catch (Exception) { }
                }
            }
            finally
            {
                Lock.ExitWriteLock();
            }

            if (count > 0)
                LogService.Info($"드라이브 용량 부족으로 대화 {count}개 삭제 완료");
            return count;
        }
        catch (Exception ex)
        {
            LogService.Warn($"디스크 용량 확인 실패: {ex.Message}");
            return 0;
        }
    }

    private static string GetFilePath(string id) => Path.Combine(ConversationsDir, $"{id}.axchat");
}
