using System.Collections.Concurrent;
using PlotLine.Models;

namespace PlotLine.Services;

public interface IManuscriptScanPreviewStore
{
    Task<ManuscriptScanState> StartAsync(int userId, int onboardingId, int projectId, int bookId);
    Task ClearAsync(int userId, int onboardingId);
    Task<ManuscriptScanState> ProgressAsync(int userId, ManuscriptScanProgress progress);
    Task<ManuscriptScanState> CompleteAsync(int userId, ManuscriptScanPreview preview);
    Task<ManuscriptScanState> FailAsync(int userId, int onboardingId, string message);
    Task<IReadOnlyList<ManuscriptScanState>> FailRunningForUserAsync(int userId, string message);
    Task<ManuscriptScanState> GetStateAsync(int userId, int onboardingId);
    Task<ManuscriptScanPreview?> GetLatestPreviewAsync(int userId, int onboardingId);
    Task<ManuscriptScanPreview?> GetPreviewAsync(int userId, Guid previewId);
    Task<ManuscriptScanReviewDecision?> GetReviewAsync(int userId, Guid previewId);
    Task<ManuscriptScanReviewDecision?> SaveReviewAsync(int userId, ManuscriptScanReviewDecision review);
    Task<OnboardingBuildProgress> BuildProgressAsync(int userId, Guid previewId, string message, int percentComplete, OnboardingManuscriptBuildResult? result = null);
    Task<OnboardingManuscriptBuildResult?> GetBuildResultAsync(int userId, Guid previewId);
    Task<OnboardingManuscriptBuildResult?> SaveBuildResultAsync(int userId, OnboardingManuscriptBuildResult result);
}

public sealed class ManuscriptScanPreviewStore : IManuscriptScanPreviewStore
{
    private readonly ConcurrentDictionary<(int UserId, int OnboardingId), ScanSession> sessions = new();
    private readonly ConcurrentDictionary<Guid, ManuscriptScanPreview> previews = new();
    private readonly ConcurrentDictionary<Guid, ManuscriptScanReviewDecision> reviews = new();
    private readonly ConcurrentDictionary<Guid, OnboardingManuscriptBuildResult> buildResults = new();
    private readonly ConcurrentDictionary<Guid, OnboardingBuildProgress> buildProgress = new();

    public Task<ManuscriptScanState> StartAsync(int userId, int onboardingId, int projectId, int bookId)
    {
        var session = new ScanSession
        {
            UserID = userId,
            OnboardingID = onboardingId,
            ProjectID = projectId,
            BookID = bookId,
            Status = ManuscriptScanStatuses.Running,
            Message = "Preparing document scan",
            PercentComplete = 0,
            UpdatedUtc = DateTime.UtcNow
        };
        sessions[(userId, onboardingId)] = session;
        return Task.FromResult(ToState(session));
    }

    public Task ClearAsync(int userId, int onboardingId)
    {
        sessions.TryRemove((userId, onboardingId), out _);
        return Task.CompletedTask;
    }

    public Task<ManuscriptScanState> ProgressAsync(int userId, ManuscriptScanProgress progress)
    {
        var key = (userId, progress.OnboardingID);
        var session = sessions.GetOrAdd(key, _ => new ScanSession
        {
            UserID = userId,
            OnboardingID = progress.OnboardingID,
            Status = ManuscriptScanStatuses.Running
        });

        session.Status = ManuscriptScanStatuses.Running;
        session.Message = Clean(progress.Message, "Scanning manuscript");
        session.PercentComplete = progress.PercentComplete;
        session.ChapterCount = progress.ChapterCount;
        session.SceneCount = progress.SceneCount;
        session.CharacterCandidateCount = progress.CharacterCandidateCount;
        session.TotalWordCount = progress.TotalWordCount;
        session.UpdatedUtc = DateTime.UtcNow;
        return Task.FromResult(ToState(session));
    }

    public Task<ManuscriptScanState> CompleteAsync(int userId, ManuscriptScanPreview preview)
    {
        var stored = preview.WithUser(userId);
        previews[stored.PreviewID] = stored;
        var session = sessions.GetOrAdd((userId, stored.OnboardingID), _ => new ScanSession
        {
            UserID = userId,
            OnboardingID = stored.OnboardingID,
            ProjectID = stored.ProjectID,
            BookID = stored.BookID
        });

        session.Status = ManuscriptScanStatuses.Complete;
        session.ReviewStatus = ManuscriptScanReviewStatuses.ScanComplete;
        session.Message = "Scan complete";
        session.PercentComplete = 100;
        session.ChapterCount = stored.ChapterCount;
        session.SceneCount = stored.SceneCount;
        session.CharacterCandidateCount = stored.CharacterCandidateCount;
        session.TotalWordCount = stored.TotalWordCount;
        session.PreviewID = stored.PreviewID;
        session.UpdatedUtc = stored.CreatedUtc;
        return Task.FromResult(ToState(session));
    }

    public Task<ManuscriptScanState> FailAsync(int userId, int onboardingId, string message)
    {
        var session = sessions.GetOrAdd((userId, onboardingId), _ => new ScanSession
        {
            UserID = userId,
            OnboardingID = onboardingId
        });
        session.Status = ManuscriptScanStatuses.Failed;
        session.Message = Clean(message, "The scan could not finish. Try reconnecting the Word Companion.");
        session.UpdatedUtc = DateTime.UtcNow;
        return Task.FromResult(ToState(session));
    }

    public Task<IReadOnlyList<ManuscriptScanState>> FailRunningForUserAsync(int userId, string message)
    {
        var failed = new List<ManuscriptScanState>();
        foreach (var pair in sessions)
        {
            if (pair.Key.UserId != userId || !string.Equals(pair.Value.Status, ManuscriptScanStatuses.Running, StringComparison.Ordinal))
            {
                continue;
            }

            pair.Value.Status = ManuscriptScanStatuses.Failed;
            pair.Value.Message = Clean(message, "The Word Companion disconnected before the scan finished. Reopen Word and try again.");
            pair.Value.UpdatedUtc = DateTime.UtcNow;
            failed.Add(ToState(pair.Value));
        }

        return Task.FromResult<IReadOnlyList<ManuscriptScanState>>(failed);
    }

    public Task<ManuscriptScanState> GetStateAsync(int userId, int onboardingId)
        => Task.FromResult(sessions.TryGetValue((userId, onboardingId), out var session)
            ? ToState(session)
            : new ManuscriptScanState());

    public Task<ManuscriptScanPreview?> GetLatestPreviewAsync(int userId, int onboardingId)
    {
        if (!sessions.TryGetValue((userId, onboardingId), out var session) || !session.PreviewID.HasValue)
        {
            return Task.FromResult<ManuscriptScanPreview?>(null);
        }

        return GetPreviewAsync(userId, session.PreviewID.Value);
    }

    public Task<ManuscriptScanPreview?> GetPreviewAsync(int userId, Guid previewId)
        => Task.FromResult(previews.TryGetValue(previewId, out var preview) && preview.UserID == userId ? preview : null);

    public Task<ManuscriptScanReviewDecision?> GetReviewAsync(int userId, Guid previewId)
    {
        if (!previews.TryGetValue(previewId, out var preview) || preview.UserID != userId)
        {
            return Task.FromResult<ManuscriptScanReviewDecision?>(null);
        }

        var review = reviews.GetOrAdd(previewId, _ => BuildDefaultReview(preview));
        return Task.FromResult<ManuscriptScanReviewDecision?>(review);
    }

    public Task<ManuscriptScanReviewDecision?> SaveReviewAsync(int userId, ManuscriptScanReviewDecision review)
    {
        if (!previews.TryGetValue(review.PreviewID, out var preview) || preview.UserID != userId)
        {
            return Task.FromResult<ManuscriptScanReviewDecision?>(null);
        }

        var stored = new ManuscriptScanReviewDecision
        {
            PreviewID = review.PreviewID,
            Status = review.Status,
            UpdatedUtc = DateTime.UtcNow,
            Chapters = review.Chapters,
            Scenes = review.Scenes,
            Characters = review.Characters
        };
        reviews[review.PreviewID] = stored;
        if (sessions.TryGetValue((userId, preview.OnboardingID), out var session))
        {
            session.ReviewStatus = stored.Status;
            session.UpdatedUtc = stored.UpdatedUtc;
        }

        return Task.FromResult<ManuscriptScanReviewDecision?>(stored);
    }

    public Task<OnboardingBuildProgress> BuildProgressAsync(int userId, Guid previewId, string message, int percentComplete, OnboardingManuscriptBuildResult? result = null)
    {
        if (!previews.TryGetValue(previewId, out var preview) || preview.UserID != userId)
        {
            return Task.FromResult(new OnboardingBuildProgress { Status = "Failed", Message = "The scan preview could not be found." });
        }

        var progress = new OnboardingBuildProgress
        {
            Status = result is null ? "Running" : "Complete",
            Message = message,
            PercentComplete = percentComplete,
            ChaptersCreated = result?.ChaptersCreated ?? 0,
            ScenesCreated = result?.ScenesCreated ?? 0,
            CharactersCreated = (result?.CharactersCreated ?? 0) + (result?.CharactersReused ?? 0),
            SceneAppearancesCreated = result?.SceneAppearancesCreated ?? 0,
            PreviewID = previewId
        };
        buildProgress[previewId] = progress;
        return Task.FromResult(progress);
    }

    public Task<OnboardingManuscriptBuildResult?> GetBuildResultAsync(int userId, Guid previewId)
        => Task.FromResult(previews.TryGetValue(previewId, out var preview)
            && preview.UserID == userId
            && buildResults.TryGetValue(previewId, out var result)
                ? result
                : null);

    public Task<OnboardingManuscriptBuildResult?> SaveBuildResultAsync(int userId, OnboardingManuscriptBuildResult result)
    {
        if (!previews.TryGetValue(result.PreviewID, out var preview) || preview.UserID != userId)
        {
            return Task.FromResult<OnboardingManuscriptBuildResult?>(null);
        }

        buildResults[result.PreviewID] = result;
        return Task.FromResult<OnboardingManuscriptBuildResult?>(result);
    }

    private static ManuscriptScanState ToState(ScanSession session)
        => new()
        {
            Status = session.Status,
            ReviewStatus = session.ReviewStatus,
            Message = session.Message,
            PercentComplete = session.PercentComplete,
            ChapterCount = session.ChapterCount,
            SceneCount = session.SceneCount,
            CharacterCandidateCount = session.CharacterCandidateCount,
            TotalWordCount = session.TotalWordCount,
            PreviewID = session.PreviewID,
            UpdatedUtc = session.UpdatedUtc
        };

    private static string Clean(string? value, string fallback)
    {
        var cleaned = (value ?? string.Empty).Trim();
        return string.IsNullOrWhiteSpace(cleaned) ? fallback : cleaned;
    }

    private static ManuscriptScanReviewDecision BuildDefaultReview(ManuscriptScanPreview preview)
        => new()
        {
            PreviewID = preview.PreviewID,
            Status = ManuscriptScanReviewStatuses.ScanComplete,
            Chapters = preview.Chapters
                .OrderBy(chapter => chapter.ChapterNumber)
                .Select(chapter => new ManuscriptScanChapterReviewDecision
                {
                    TemporaryChapterKey = chapter.TemporaryChapterKey,
                    Include = !string.IsNullOrWhiteSpace(chapter.Title) && chapter.WordCount > 0,
                    Title = chapter.Title,
                    ChapterNumber = chapter.ChapterNumber
                })
                .ToList(),
            Scenes = preview.Scenes
                .OrderBy(scene => scene.TemporaryChapterKey)
                .ThenBy(scene => scene.SceneNumberWithinChapter)
                .Select(scene => new ManuscriptScanSceneReviewDecision
                {
                    TemporarySceneKey = scene.TemporarySceneKey,
                    TemporaryChapterKey = scene.TemporaryChapterKey,
                    Include = true,
                    Title = string.IsNullOrWhiteSpace(scene.Title) ? $"Scene {scene.SceneNumberWithinChapter}" : scene.Title,
                    SceneNumberWithinChapter = scene.SceneNumberWithinChapter
                })
                .ToList(),
            Characters = preview.CharacterCandidates
                .Select(candidate => new ManuscriptScanCharacterReviewDecision
                {
                    TemporaryCharacterKey = candidate.TemporaryCharacterKey,
                    Include = string.Equals(candidate.Category, "ProbableCharacter", StringComparison.OrdinalIgnoreCase),
                    Name = candidate.Name,
                    Category = candidate.Category
                })
                .ToList()
        };

    private sealed class ScanSession
    {
        public int UserID { get; init; }
        public int OnboardingID { get; init; }
        public int ProjectID { get; init; }
        public int BookID { get; init; }
        public string Status { get; set; } = ManuscriptScanStatuses.NotStarted;
        public string ReviewStatus { get; set; } = ManuscriptScanReviewStatuses.ScanComplete;
        public string Message { get; set; } = string.Empty;
        public int? PercentComplete { get; set; }
        public int ChapterCount { get; set; }
        public int SceneCount { get; set; }
        public int CharacterCandidateCount { get; set; }
        public int TotalWordCount { get; set; }
        public Guid? PreviewID { get; set; }
        public DateTime? UpdatedUtc { get; set; }
    }
}

file static class ManuscriptScanPreviewExtensions
{
    public static ManuscriptScanPreview WithUser(this ManuscriptScanPreview preview, int userId)
        => new()
        {
            PreviewID = preview.PreviewID == Guid.Empty ? Guid.NewGuid() : preview.PreviewID,
            UserID = userId,
            OnboardingID = preview.OnboardingID,
            ProjectID = preview.ProjectID,
            BookID = preview.BookID,
            Source = preview.Source,
            DocumentTitle = preview.DocumentTitle,
            CompanionDocumentIdentifier = preview.CompanionDocumentIdentifier,
            TotalWordCount = preview.TotalWordCount,
            ChapterCount = preview.ChapterCount,
            SceneCount = preview.SceneCount,
            CharacterCandidateCount = preview.CharacterCandidateCount,
            CreatedUtc = preview.CreatedUtc == default ? DateTime.UtcNow : preview.CreatedUtc,
            Chapters = preview.Chapters,
            Scenes = preview.Scenes,
            CharacterCandidates = preview.CharacterCandidates
        };
}
