using PlotLine.Models;

namespace PlotLine.Services;

public sealed class CharacterAgeDisplayResult
{
    public bool HasExactAge { get; init; }
    public string? Label { get; init; }
    public string? DisplayText { get; init; }
}

public static class CharacterAgeDisplayService
{
    public static CharacterAgeDisplayResult GetAgeForScene(SceneCharacter character, string? sceneTimeModeName, DateTime? sceneStartDateTime)
    {
        var effectiveDate = ResolveEffectiveSceneDate(sceneTimeModeName, sceneStartDateTime);
        if (character.BirthDate.HasValue && effectiveDate.HasValue)
        {
            return new CharacterAgeDisplayResult
            {
                HasExactAge = true,
                Label = "Age in scene",
                DisplayText = FormatExactAge(character.BirthDate.Value.Date, effectiveDate.Value.Date)
            };
        }

        if (character.AgeAtSeriesStart.HasValue)
        {
            return new CharacterAgeDisplayResult
            {
                Label = "Approximate age",
                DisplayText = $"About {character.AgeAtSeriesStart.Value}"
            };
        }

        return new CharacterAgeDisplayResult();
    }

    public static CharacterAgeDisplayResult GetAgeForScene(Character character, string? sceneTimeModeName, DateTime? sceneStartDateTime)
    {
        var effectiveDate = ResolveEffectiveSceneDate(sceneTimeModeName, sceneStartDateTime);
        if (character.BirthDate.HasValue && effectiveDate.HasValue)
        {
            return new CharacterAgeDisplayResult
            {
                HasExactAge = true,
                Label = "Age in scene",
                DisplayText = FormatExactAge(character.BirthDate.Value.Date, effectiveDate.Value.Date)
            };
        }

        if (character.AgeAtSeriesStart.HasValue)
        {
            return new CharacterAgeDisplayResult
            {
                Label = "Approximate age",
                DisplayText = $"About {character.AgeAtSeriesStart.Value}"
            };
        }

        return new CharacterAgeDisplayResult();
    }

    public static DateTime? ResolveEffectiveSceneDate(string? timeModeName, DateTime? startDateTime)
    {
        if (!startDateTime.HasValue)
        {
            return null;
        }

        return string.Equals(timeModeName, "Exact Date", StringComparison.OrdinalIgnoreCase)
            || string.Equals(timeModeName, "Exact DateTime", StringComparison.OrdinalIgnoreCase)
            || string.Equals(timeModeName, "Exact Date and Time", StringComparison.OrdinalIgnoreCase)
                ? startDateTime.Value.Date
                : null;
    }

    public static string FormatExactAge(DateTime birthDate, DateTime sceneDate)
    {
        var (years, months) = CalculateExactAge(birthDate, sceneDate);

        return months == 0
            ? $"{years} year{Pluralise(years)}"
            : $"{years} year{Pluralise(years)}, {months} month{Pluralise(months)}";
    }

    public static (int Years, int Months) CalculateExactAge(DateTime birthDate, DateTime sceneDate)
    {
        var years = sceneDate.Year - birthDate.Year;
        if (sceneDate < birthDate.AddYears(years))
        {
            years--;
        }

        if (years < 0)
        {
            years = 0;
        }

        var lastBirthday = birthDate.AddYears(years);
        var months = ((sceneDate.Year - lastBirthday.Year) * 12) + sceneDate.Month - lastBirthday.Month;
        if (sceneDate.Day < lastBirthday.Day)
        {
            months--;
        }

        if (months < 0)
        {
            months = 0;
        }

        return (years, months);
    }

    private static string Pluralise(int value) => value == 1 ? string.Empty : "s";
}
