using System;
using UnityEngine;
using UnityEngine.UIElements;

namespace KitsuneCafe.System
{
    public enum TimeUnit : byte
    {
        Ticks,
        Milliseconds,
        Seconds,
        Minutes,
        Hours,
        Days
    }

    [Serializable]
    public struct Duration : IComparable, IComparable<Duration>, IComparable<TimeSpan>, IEquatable<Duration>, IEquatable<TimeSpan>
    {
        [SerializeField, HideInInspector]
        private long value;

        [SerializeField]
        private TimeUnit unit;

        public Duration(long ticks, TimeUnit unit)
        {
            value = ticks;
            this.unit = unit;
        }

        public Duration(long ticks) : this(ticks, TimeUnit.Ticks) { }

        public static implicit operator Duration(TimeSpan ts) => new(ts.Ticks, TimeUnit.Ticks);
        public static implicit operator TimeSpan(Duration duration) => duration.AsTimeSpan();
        public static implicit operator TimeValue(Duration duration) => duration.AsTimeValue();
        public static Duration operator +(Duration lhs, Duration rhs) => lhs.AsTimeSpan() + rhs.AsTimeSpan();

        public readonly TimeSpan AsTimeSpan() => new(value);
        public readonly TimeValue AsTimeValue() => new((float)Into(TimeUnit.Seconds));

        public static Duration From(long value, TimeUnit unit)
        {
            var ticks = unit switch
            {
                TimeUnit.Ticks => value,
                TimeUnit.Milliseconds => TimeSpan.TicksPerMillisecond * value,
                TimeUnit.Seconds => TimeSpan.TicksPerSecond * value,
                TimeUnit.Minutes => TimeSpan.TicksPerMinute * value,
                TimeUnit.Hours => TimeSpan.TicksPerHour * value,
                TimeUnit.Days => TimeSpan.TicksPerHour * value,
                var x => throw new ArgumentException($"{x} is not a valid TimeUnit.")
            };

            return new Duration(ticks, unit);
        }

        public static Duration From(double value, TimeUnit unit)
        {
            var ticks = unit switch
            {
                TimeUnit.Ticks => value,
                TimeUnit.Milliseconds => TimeSpan.TicksPerMillisecond * value,
                TimeUnit.Seconds => TimeSpan.TicksPerSecond * value,
                TimeUnit.Minutes => TimeSpan.TicksPerMinute * value,
                TimeUnit.Hours => TimeSpan.TicksPerHour * value,
                TimeUnit.Days => TimeSpan.TicksPerHour * value,
                var x => throw new ArgumentException($"{x} is not a valid TimeUnit.")
            };

            return new Duration(Convert.ToInt64(ticks), unit);
        }

        public static double ToDisplayValue(long ticks, TimeUnit unit)
        {
            decimal value = unit switch
            {
                TimeUnit.Ticks => ticks,
                TimeUnit.Milliseconds => (decimal)ticks / TimeSpan.TicksPerMillisecond,
                TimeUnit.Seconds => (decimal)ticks / TimeSpan.TicksPerSecond,
                TimeUnit.Minutes => (decimal)ticks / TimeSpan.TicksPerMinute,
                TimeUnit.Hours => (decimal)ticks / TimeSpan.TicksPerHour,
                TimeUnit.Days => (decimal)ticks / TimeSpan.TicksPerHour,
                var x => throw new ArgumentException($"{x} is not a valid TimeUnit.")
            };

            return Convert.ToDouble(value);
        }

        public static long FromDisplayValue(double value, TimeUnit unit)
        {
            var ticks = unit switch
            {
                TimeUnit.Ticks => value,
                TimeUnit.Milliseconds => TimeSpan.TicksPerMillisecond * value,
                TimeUnit.Seconds => TimeSpan.TicksPerSecond * value,
                TimeUnit.Minutes => TimeSpan.TicksPerMinute * value,
                TimeUnit.Hours => TimeSpan.TicksPerHour * value,
                TimeUnit.Days => TimeSpan.TicksPerHour * value,
                var x => throw new ArgumentException($"{x} is not a valid TimeUnit.")
            };

            return Convert.ToInt64(ticks);
        }

        public readonly double Into() => Into(unit);
        public readonly double Into(TimeUnit unit)
        {
            return ToDisplayValue(value, unit);
        }

        public readonly int CompareTo(object obj)
        {
            return obj switch
            {
                Duration d => CompareTo(d),
                TimeSpan ts => CompareTo(ts),
                TimeValue tv => CompareTo(tv),
                var value => throw new ArgumentException($"{value} is not comparable to Duration")
            };
        }

        public static bool Equals(Duration a, Duration b)
        {
            return a.value == b.value;
        }

        public static int Compare(Duration a, Duration b)
        {
            return a.value.CompareTo(b.value);
        }

        public readonly int CompareTo(Duration other)
        {
            return Compare(this, other);
        }

        public readonly int CompareTo(TimeSpan other)
        {
            return TimeSpan.Compare(this, other);
        }

        public readonly bool Equals(Duration other)
        {
            return Equals(this, other);
        }

        public readonly bool Equals(TimeSpan other)
        {
            return other.Equals(this);
        }

        public override readonly bool Equals(object obj)
        {
            return (obj is Duration d && Equals(this, d))
                || (obj is TimeSpan ts && Equals(this, ts));
        }

        public override readonly int GetHashCode()
        {
            return HashCode.Combine(value);
        }

        public override readonly string ToString()
        {
            return $"{value} {nameof(unit)}";
        }
    }
}
