using PlotLine.Models;

namespace PlotLine.Services;

public sealed class CharacterAgeContinuityScene
{
    public int SceneID { get; init; }
    public int ProjectID { get; init; }
    public int? BookID { get; init; }
    public int? ChapterID { get; init; }
    public decimal? ChapterNumber { get; init; }
    public string? ChapterTitle { get; init; }
    public decimal? SceneNumber { get; init; }
    public string SceneTitle { get; init; } = string.Empty;
    public int ChapterSortOrder { get; init; }
    public int SceneSortOrder { get; init; }
    public string TimeModeName { get; init; } = string.Empty;
    public DateTime? StartDateTime { get; init; }
}

public static class CharacterAgeContinuityService
{
    public static IReadOnlyList<ContinuityWarning> GetWarningsForScene(
        CharacterAgeContinuityScene currentScene,
        IReadOnlyList<CharacterAgeContinuityScene> scenes,
        IReadOnlyList<SceneCharacter> appearances,
        IReadOnlyList<SceneCharacter> currentSceneCharacters)
    {
        var warnings = new List<ContinuityWarning>();
        warnings.AddRange(GetApproximateAgeMismatchWarnings(currentScene, currentSceneCharacters));
        warnings.AddRange(GetBirthdayTransitionWarnings(currentScene, scenes, appearances));
        return warnings;
    }

    public static IReadOnlyList<ContinuityWarning> GetApproximateAgeMismatchWarnings(
        CharacterAgeContinuityScene scene,
        IReadOnlyList<SceneCharacter> sceneCharacters)
    {
        var sceneDate = CharacterAgeDisplayService.ResolveEffectiveSceneDate(scene.TimeModeName, scene.StartDateTime);
        if (!sceneDate.HasValue)
        {
            return [];
        }

        return sceneCharacters
            .Where(character => character.BirthDate.HasValue && character.AgeAtSeriesStart.HasValue)
            .Select(character => new
            {
                Character = character,
                ExactAge = CharacterAgeDisplayService.CalculateExactAge(character.BirthDate!.Value.Date, sceneDate.Value.Date)
            })
            .Where(item => Math.Abs(item.Character.AgeAtSeriesStart!.Value - item.ExactAge.Years) > 1)
            .Select(item => CreateWarning(
                scene,
                item.Character.CharacterID,
                "Warning",
                "Character Age",
                "Character profile age differs significantly from calculated age.",
                $"Profile states approximately {item.Character.AgeAtSeriesStart} years old. Calculated age in this scene is {CharacterAgeDisplayService.FormatExactAge(item.Character.BirthDate!.Value.Date, sceneDate.Value.Date)}."))
            .ToList();
    }

    public static IReadOnlyList<ContinuityWarning> GetBirthdayTransitionWarnings(
        CharacterAgeContinuityScene currentScene,
        IReadOnlyList<CharacterAgeContinuityScene> scenes,
        IReadOnlyList<SceneCharacter> appearances)
    {
        var orderedScenes = scenes
            .Select(scene => new
            {
                Scene = scene,
                EffectiveDate = CharacterAgeDisplayService.ResolveEffectiveSceneDate(scene.TimeModeName, scene.StartDateTime)
            })
            .Where(scene => scene.EffectiveDate.HasValue)
            .OrderBy(scene => scene.EffectiveDate!.Value)
            .ThenBy(scene => scene.Scene.ChapterSortOrder)
            .ThenBy(scene => scene.Scene.SceneSortOrder)
            .ThenBy(scene => scene.Scene.SceneID)
            .ToList();

        var currentIndex = orderedScenes.FindIndex(scene => scene.Scene.SceneID == currentScene.SceneID);
        if (currentIndex < 0 || currentIndex == orderedScenes.Count - 1)
        {
            return [];
        }

        var current = orderedScenes[currentIndex];
        var next = orderedScenes[currentIndex + 1];
        if (next.EffectiveDate!.Value <= current.EffectiveDate!.Value)
        {
            return [];
        }

        var nextSceneCharacterIds = appearances
            .Where(appearance => appearance.SceneID == next.Scene.SceneID)
            .Select(appearance => appearance.CharacterID)
            .ToHashSet();

        return appearances
            .Where(appearance => appearance.SceneID == current.Scene.SceneID
                && appearance.BirthDate.HasValue
                && nextSceneCharacterIds.Contains(appearance.CharacterID))
            .GroupBy(appearance => appearance.CharacterID)
            .Select(group => group.First())
            .Select(appearance => new
            {
                Appearance = appearance,
                Birthday = GetNextBirthdayAfter(appearance.BirthDate!.Value.Date, current.EffectiveDate!.Value.Date)
            })
            .Where(item => item.Birthday <= next.EffectiveDate!.Value.Date)
            .Select(item =>
            {
                var age = CharacterAgeDisplayService.CalculateExactAge(item.Appearance.BirthDate!.Value.Date, item.Birthday).Years;
                return CreateWarning(
                    current.Scene,
                    item.Appearance.CharacterID,
                    "Info",
                    "Character Age",
                    $"{item.Appearance.CharacterName} turns {age} between this scene and the next.",
                    $"Birthday falls on {item.Birthday:dd MMM yyyy}. Next scene: {next.Scene.SceneTitle}.");
            })
            .ToList();
    }

    private static DateTime GetNextBirthdayAfter(DateTime birthDate, DateTime sceneDate)
    {
        var birthday = new DateTime(sceneDate.Year, birthDate.Month, birthDate.Day);
        return birthday > sceneDate ? birthday : birthday.AddYears(1);
    }

    private static ContinuityWarning CreateWarning(
        CharacterAgeContinuityScene scene,
        int? characterId,
        string severityName,
        string warningTypeName,
        string message,
        string details) =>
        new()
        {
            ContinuityWarningID = 0,
            ProjectID = scene.ProjectID,
            BookID = scene.BookID,
            ChapterID = scene.ChapterID,
            ChapterNumber = scene.ChapterNumber,
            ChapterTitle = scene.ChapterTitle,
            SceneID = scene.SceneID,
            SceneNumber = scene.SceneNumber,
            SceneTitle = scene.SceneTitle,
            EntityType = "Character",
            EntityID = characterId,
            WarningTypeName = warningTypeName,
            SeverityName = severityName,
            Message = message,
            Details = details,
            CreatedDate = DateTime.UtcNow,
            LastDetectedDate = DateTime.UtcNow
        };
}
