using Newtonsoft.Json.Linq;
using EDPlayerJournal.Entries;
using System.Runtime.CompilerServices;

namespace EDPlayerJournal;

public class FactionEffect {
    public string? Effect { get; set; }
    public string? EffectLocalised { get; set; }
    public string? Trend { get; set; }

    public static FactionEffect FromJSON(JObject obj) {
        FactionEffect effect = new FactionEffect();

        effect.Effect = obj.Value<string?>("Effect");
        effect.EffectLocalised = obj.Value<string?>("Effect_Localised");
        effect.Trend = obj.Value<string?>("Trend");

        return effect;
    }
}

public class MissionInfluence {
    /// <summary>
    /// System Address this influence happened in.
    /// </summary>
    public ulong? SystemAddress { get; set; }

    /// <summary>
    /// Generic description of the trend (up or down)
    /// </summary>
    public string? Trend { get; set; }

    /// <summary>
    /// Influence gained in plusses
    /// </summary>
    public string Influence { get; set; } = string.Empty;

    public long InfluenceAmount {
        get {
            string trend = TrendAdjustedInfluence;
            return (long)
                   (trend.Count(x => x == '-') * -1) +
                    trend.Count(x => x == '+')
                   ;
        }
    }

    /// <summary>
    /// Returns how much influence was made, represented in pluses for positive influence,
    /// and minuses with negative influence. This takes Trend (up, bad etc.) into account.
    /// </summary>
    public string TrendAdjustedInfluence {
        get {
            if (!string.IsNullOrEmpty(Trend) &&
                Trend.Contains("bad", StringComparison.OrdinalIgnoreCase)) {
                return new string('-', Influence.Length);
            } else {
                return new string('+', Influence.Length);
            }
        }
    }

    public static MissionInfluence FromJSON(JObject obj) {
        MissionInfluence missionInfluence = new MissionInfluence();

        missionInfluence.SystemAddress = obj.Value<ulong?>("SystemAddress");
        missionInfluence.Trend = obj.Value<string?>("Trend");
        missionInfluence.Influence = obj.Value<string?>("Influence") ?? "";

        return missionInfluence;
    }
}

public class MissionFactionEffects {
    /// <summary>
    /// Faction in question
    /// </summary>
    public string? Faction { get; set; }

    /// <summary>
    /// Reputation trend
    /// </summary>
    public string? ReputationTrend { get; set; }

    /// <summary>
    /// Reputation in pluses.
    /// </summary>
    public string Reputation { get; set; } = string.Empty;

    /// <summary>
    /// List of economic and security affects.
    /// </summary>
    public List<FactionEffect> Effects { get; set; } = new List<FactionEffect>();

    /// <summary>
    /// Influence gained.
    /// </summary>
    public List<MissionInfluence> Influences { get; set; } = new List<MissionInfluence>();

    /// <summary>
    /// Whether this affect is for an empty, or non-existant faction.
    /// </summary>
    public bool IsEmptyFaction {
        get { return string.IsNullOrEmpty(Faction); }
    }

    public static MissionFactionEffects FromJSON(JObject j) {
        MissionFactionEffects o = new MissionFactionEffects();

        o.Faction = j.Value<string>("Faction");

        o.ReputationTrend = j.Value<string?>("ReputationTrend");
        o.Reputation = j.Value<string?>("Reputation") ?? string.Empty;

        JArray? effects = j.Value<JArray?>("Effects");
        if (effects != null) {
            foreach (JObject effect in effects.Children().OfType<JObject>()) {
                FactionEffect e = FactionEffect.FromJSON(effect);
                o.Effects.Add(e);
            }
        }

        JArray? influences = j.Value<JArray?>("Influence");
        if (influences != null) {
            foreach (JObject influence in influences.Children().OfType<JObject>()) {
                MissionInfluence missionInfluence = MissionInfluence.FromJSON(influence);
                o.Influences.Add(missionInfluence);
            }
        }

        return o;
    }
}

public class Mission : IComparable<Mission> {
    /// <summary>
    /// Passenger type for refugees
    /// </summary>
    public static readonly string PassengerTypeRefugee = "Refugee";

    public ulong MissionID { get; set; } = 0;

    /// <summary>
    /// Name of the mission, in machine readable format.
    /// </summary>
    public string? Name { get; set; }

    /// <summary>
    /// Localised, human readable mission name.
    /// </summary>
    public string? LocalisedName { get; set; }

    /// <summary>
    /// Target of the mission. Optional.
    /// </summary>
    public string? Target { get; set; }

    /// <summary>
    /// Target of the mission as a localised human readable string.
    /// </summary>
    public string? TargetLocalised { get; set; }

    /// <summary>
    /// Target system of the mission
    /// </summary>
    public string? DestinationSystem { get; set; }

    /// <summary>
    /// Destination station.
    /// </summary>
    public string? DestinationStation { get; set; }

    /// <summary>
    /// Destination settlement
    /// </summary>
    public string? DestinationSettlement { get; set; }

    /// <summary>
    /// In case of redirection, these values denote the new system.
    /// </summary>
    public string? NewDestinationSystem { get; set; }

    /// <summary>
    /// In case of redirection, these values denote the new station.
    /// </summary>
    public string? NewDestinationStation { get; set; }

    /// <summary>
    /// Faction offering the mission.
    /// </summary>
    public string? Faction { get; set; }

    /// <summary>
    /// Target faction (for example for courier missions).
    /// </summary>
    public string? TargetFaction { get; set; }

    /// <summary>
    /// Whether the mission is a wing message.
    /// </summary>
    public bool Wing { get; set; } = false;

    /// <summary>
    /// Expiry date for the mission.
    /// </summary>
    public string? Expiry { get; set; }

    /// <summary>
    /// Certain missions have an expires number. No one knows what that is.
    /// </summary>
    public ulong? Expires { get; set; }

    /// <summary>
    /// Influence reward offered. This is for accepting missions only, see the
    /// mission effects for actual effects once the mission is complete.
    /// </summary>
    public string? Influence { get; set; }

    /// <summary>
    /// Reputation reward offered.
    /// </summary>
    public string? Reputation { get; set; }

    /// <summary>
    /// Number of kills required for massacre missions.
    /// </summary>
    public ulong? KillCount { get; set; }

    /// <summary>
    /// Monatery reward offered for the mission. Optional, as donate missions don't give
    /// a monatery reward.
    /// </summary>
    public ulong? Reward { get; set; }

    /// <summary>
    /// Amount donated for donation missions, optional. If this is null, then
    /// the mission was not a donation mission.
    /// </summary>
    public ulong? Donation { get; set; }

    /// <summary>
    /// Actual amount donated.
    /// </summary>
    public ulong? Donated { get; set; }

    /// <summary>
    /// Commodity delivered, or donated. Optional, if this is null, then no
    /// commodity was donated or delivered.
    /// </summary>
    public string? Commodity { get; set; }

    /// <summary>
    /// Amount of the commodity donated or delivered. Optional.
    /// </summary>
    public ulong? Count { get; set; }

    /// <summary>
    /// How many passengers are being transported.
    /// </summary>
    public ulong? PassengerCount { get; set; }

    /// <summary>
    /// If the passengers are VIPs.
    /// </summary>
    public bool? PassengerVIPs { get; set; }

    /// <summary>
    /// If the passengers are wanted.
    /// </summary>
    public bool? PassengerWanted { get; set; }

    /// <summary>
    /// What sort of passengers are being transported.
    /// </summary>
    public string? PassengerType { get; set; }

    /// <summary>
    /// Affect this mission had on factions.
    /// </summary>
    public List<MissionFactionEffects> FactionEffects { get; set; } = new List<MissionFactionEffects>();

    /// <summary>
    /// Returns true if the name is an on foot mission.
    /// </summary>
    public bool IsOnFoot {
        get {
            if (string.IsNullOrEmpty(Name)) {
                return false;
            }

            return Name.Contains("OnFoot");
        }
    }

    public bool IsPassengerMission {
        get {
            if (PassengerCount == null || PassengerCount == 0) {
                return false;
            }

            return true;
        }
    }

    public bool IsRescueMission {
        get {
            if (!IsPassengerMission) {
                return false;
            }

            if (string.Compare(PassengerType, PassengerTypeRefugee) == 0) {
                return true;
            }

            return false;
        }
    }

    /// <summary>
    /// Returns a friendly human-readable name for the mission. If a localised name is available
    /// it will use that, baring that it will check EnglishMissionNames for a translation, and
    /// if that ain't available either, it will just use the internal Name.
    /// </summary>
    public string FriendlyName {
        get {
            if (!string.IsNullOrEmpty(LocalisedName)) {
                return LocalisedName;
            }

            if (string.IsNullOrEmpty(Name)) {
                return "Unknown Mission";
            }

            string? translate = EnglishMissionNames.Translate(Name);
            if (!string.IsNullOrEmpty(translate)) {
                return translate;
            }

            return Name;
        }
    }

    public int CompareTo(Mission? other) {
        if (other == null) {
            return 1;
        }

        return MissionID.CompareTo(other.MissionID);
    }

    private static Mission FromJSON(JObject o) {
        Mission mission = new Mission();

        mission.MissionID = o.Value<ulong?>("MissionID") ?? 0;

        mission.Reputation = o.Value<string>("Reputation");
        mission.Influence = o.Value<string>("Influence");

        mission.DestinationSystem = o.Value<string>("DestinationSystem");
        mission.DestinationSettlement = o.Value<string>("DestinationSettlement");
        mission.DestinationStation = o.Value<string>("DestinationStation");

        mission.NewDestinationSystem = o.Value<string>("NewDestinationSystem");
        mission.NewDestinationStation = o.Value<string>("NewDestinationSystem");

        mission.Reward = o.Value<ulong?>("Reward");

        mission.Target = o.Value<string>("Target");
        mission.TargetLocalised = o.Value<string>("Target_Localised");

        mission.Expiry = o.Value<string?>("Expiry");
        mission.Expires = o.Value<ulong?>("Expires");
        mission.Wing = o.Value<bool?>("Wing") ?? false;

        mission.Name = o.Value<string>("Name");
        mission.LocalisedName = o.Value<string>("LocalisedName");

        mission.Faction = o.Value<string?>("Faction");
        mission.TargetFaction = o.Value<string?>("TargetFaction");

        mission.Donation = o.Value<ulong?>("Donation");
        mission.Donated = o.Value<ulong?>("Donated");

        mission.PassengerCount = o.Value<ulong?>("PassengerCount");
        mission.PassengerVIPs = o.Value<bool?>("PassengerVIPs");
        mission.PassengerWanted = o.Value<bool?>("PassengerWanted");
        mission.PassengerType = o.Value<string>("PassengerType");

        mission.KillCount = o.Value<ulong?>("KillCount");

        JArray? factionEffects = o.Value<JArray?>("FactionEffects");
        if (factionEffects != null) {
            foreach (JObject effect in factionEffects.Children().OfType<JObject>()) {
                MissionFactionEffects factionEffect = MissionFactionEffects.FromJSON(effect);
                mission.FactionEffects.Add(factionEffect);
            }
        }

        return mission;
    }

    /// <summary>
    /// Returns a list of all affected factions.
    /// </summary>
    public string[] AffectedFactions {
        get {
            return FactionEffects
                .Where(x => !string.IsNullOrEmpty(x.Faction))
                .Select(x => (x.Faction ?? string.Empty))
                .ToArray()
                ;
        }
    }

    /// <summary>
    /// Returns the influence for a given faction in a given star system.
    /// </summary>
    /// <param name="faction">Faction name in question.</param>
    /// <param name="systemaddr">Star System address</param>
    /// <returns>null if no entry was found, or a string denoting pluses for the amount influence gained.</returns>
    public MissionInfluence[]? GetInfluenceForFaction(string faction, ulong systemaddr) {
        var results = FactionEffects
            .Where(x => string.Compare(x.Faction, faction) == 0)
            .SelectMany(x => x.Influences)
            .Where(x => (x.SystemAddress != null && x.SystemAddress == systemaddr))
            .Select(x => x)
            .ToArray()
            ;

        if (results == null || results.Length == 0) {
            return new MissionInfluence[0];
        }

        return results;
    }

    /// <summary>
    /// A convenient Dictionary containing all influences given out by faction,
    /// then by system address and then by influence handed out. Influence can
    /// be either a series of "+" for positive influence, or "-" for negative
    /// influence.
    /// </summary>
    public Dictionary<string, Dictionary<ulong, MissionInfluence>> Influences {
        get {
            return FactionEffects
                .Where(x => x.Faction != null)
                .ToDictionary(
                    x => (x.Faction ?? string.Empty),
                    x => x.Influences
                            .Where(x => x.SystemAddress != null)
                            .ToDictionary(
                                x => (x.SystemAddress ?? 0), 
                                x => x
                                )
                    );
        }
    }

    public static Mission FromMissionAccepted(JObject o) {
        return FromJSON(o);
    }

    public static Mission FromMissionAbandoned(JObject o) {
        return FromJSON(o);
    }

    public static Mission FromMissionFailed(JObject o) {
        return FromJSON(o);
    }

    public static Mission FromMissionCompleted(JObject o) {
        return FromJSON(o);
    }
}
