using Newtonsoft.Json;

namespace Pilz;

[method: JsonConstructor]
public class AppVersion(Version version, int build, AppChannel channel) : IComparable, IComparable<AppVersion>
{
    // P r o p e r t i e s

    [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))]
    public Version Version { get; set; } = version;
    [JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
    public AppChannel Channel { get; set; } = channel;
    public int Build { get; set; } = build;

    // C o n s t r u c t o r s

    public AppVersion() : this(new Version("1.0.0.0"))
    {
    }

    public AppVersion(Version version) : this(version, 1, AppChannel.Stable)
    {
    }

    // F e a t u r e s

    public override string ToString()
    {
        if (Channel == AppChannel.Stable && Build == 1)
            return Version.ToString();

        return $"{Version} {Channel} {Build}";
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(this, obj))
            return true;
        if (obj is not AppVersion applicationVersion)
            return false;
        return applicationVersion == this;
    }

    public override int GetHashCode()
    {
        return ToString().GetHashCode();
    }

    public static bool TryParse(string input, out AppVersion version)
    {
        try
        {
            version = Parse(input);
        }
        catch (Exception)
        {
            version = null;
        }
        return version != null;
    }

    public static AppVersion Parse(string input)
    {
        if (string.IsNullOrWhiteSpace(input))
            throw new FormatException();

        if (input.StartsWith("version", StringComparison.InvariantCultureIgnoreCase))
            input = input[7..];

        var splitted = input.Trim().Split(' ');

        if (splitted.Length < 1 || !Version.TryParse(splitted[0], out Version? version) || version == null)
            throw new FormatException();

        if (splitted.Length < 2 || !Enum.TryParse(splitted[1], out AppChannel channel))
            channel = AppChannel.Stable;

        if (splitted.Length < 3 || !int.TryParse(splitted[2], out int build))
            build = 1;

        return new AppVersion(version, build, channel);
    }

    // C o m p a r e

    public int CompareTo(object appVersion)
    {
        return CompareTo(appVersion as AppVersion);
    }

    public int CompareTo(AppVersion appVersion)
    {
        if (appVersion is null)
            return 1;

        if (Version != appVersion.Version)
        {
            if (Version > appVersion.Version)
                return 1;
            return -1;
        }

        if (Channel != appVersion.Channel)
        {
            if (Channel < appVersion.Channel)
                return 1;
            return -1;
        }

        if (Build != appVersion.Build)
        {
            if (Build > appVersion.Build)
                return 1;
            return -1;
        }

        return 0;
    }

    // O p e r a t o r s

    public static bool operator >(AppVersion a, AppVersion b)
    {
        return a.Version > b.Version || a.Version == b.Version && (a.Channel < b.Channel || (a.Channel == b.Channel && a.Build > b.Build));
    }

    public static bool operator <(AppVersion a, AppVersion b)
    {
        return a.Version < b.Version || a.Version == b.Version && (a.Channel > b.Channel || (a.Channel == b.Channel && a.Build < b.Build));
    }

    public static bool operator ==(AppVersion a, AppVersion b)
    {
        return a.Version == b.Version && a.Channel == b.Channel && a.Build == b.Build;
    }

    public static bool operator !=(AppVersion a, AppVersion b)
    {
        return a.Version != b.Version || a.Channel != b.Channel || a.Build != b.Build;
    }

    public static bool operator >=(AppVersion a, AppVersion b)
    {
        return a == b || a > b;
    }

    public static bool operator <=(AppVersion a, AppVersion b)
    {
        return a == b || a < b;
    }
}