using System;
using System.Collections.Generic;
using System.Linq;
using Verse;
using RimWorld;
using Verse.AI.Group;
using rjw.Modules.Shared.Logs;
using Mono.CompilerServices.SymbolWriter;

namespace SCE
{
    /* Generates LewdWedding obligations a set number of ticks after engagement plus an MTB.
     * Note that RitualRoleBetrothed *doesn't* have an MTB, so a sufficiently eager player
     * can start the ritual without worrying about RNG
     */
    public class RitualObligationTrigger_LewdWedding : RitualObligationTrigger
    {
        public const int ticksToMarriage = GenDate.TicksPerDay * 10;

        private static readonly ILog log = LogManager.GetLogger<RitualObligationTrigger_LewdWedding, DebugLogProvider>();

        // We want to check whether a wedding is due every tick, so hopefully caching whether we
        // have any engagements to check will ease the performance impact? Maybe?
        private bool hasEngagement = true;

        private int _nextOpportunityTick = -1;
        public int NextOpportunityTick
        {
            get
            {
                if (_nextOpportunityTick == -1 && hasEngagement)
                {
                    if (engagements.Count == 0)
                    {
                        hasEngagement = false;
                    }
                    else
                    {
                        _nextOpportunityTick = engagements[0].startTicks + ticksToMarriage;
                    }
                }
                return _nextOpportunityTick;
            }

            private set => _nextOpportunityTick = value;
        }

        private List<PawnRelationInfo> engagements = new();
        public List<PawnRelationInfo> Engagements => engagements;

        // Populate engagements if the precept is added mid-game.
        public override void Init(RitualObligationTriggerProperties props)
        {
            base.Init(props);
            foreach (var pawn in Find.Maps.SelectMany(m => m.PlayerPawnsForStoryteller).Where(pawn => pawn.Ideo == ritual.ideo))
            {
                foreach (var engagement in pawn.relations.DirectRelations.Where(rel => rel.def == PawnRelationDefOf.Fiance))
                {
                    Notify_PawnEngaged(pawn, engagement);
                }
            }
        }

        public override void Tick()
        {
            if (NextOpportunityTick >= 0 && Find.TickManager.TicksGame > NextOpportunityTick)
            {
                var (pawn, otherPawn) = engagements[0];
                log.Message($"Attempting to create {ritual.Label} opportunity for {pawn} and {otherPawn}");
                if (pawn == null || otherPawn == null)
                {
                    log.Message("Aborting - One of the pawns is null!");
                    engagements.RemoveAt(0);
                    NextOpportunityTick = -1;
                    return;
                }

                var obligation = new RitualObligation(ritual, pawn, otherPawn, expires: false);
                ritual.AddObligation(obligation);

                log.Message("Successfully created the opportunity");

                engagements.RemoveAt(0);
                NextOpportunityTick = -1;
            }
        }

        public override void Notify_MemberGained(Pawn p)
        {
            foreach (var relation in p.relations.DirectRelations.Where(r => r.def == PawnRelationDefOf.Fiance))
            {
                Notify_PawnEngaged(p, relation);
            }
        }

        public override void Notify_MemberGenerated(Pawn p)
        {
            Notify_MemberGained(p);
        }

        public override void Notify_MemberLost(Pawn p)
        {
            RemoveEngagementsFor(p, totalRemoval: false);
        }

        public override void Notify_MemberDied(Pawn p)
        {
            RemoveEngagementsFor(p);
        }

        public virtual void Notify_PawnEngaged(Pawn p, DirectPawnRelation newEngagement)
        {
            // Check if we already have an engagement involving the same pawns
            foreach (var engagement in engagements)
            {
                if (engagement.IsBetween(p, newEngagement.otherPawn))
                {
                    Log.Warning("Tried to register the same engagement twice.");
                    return;
                }
            }

            log.Message($"Engagement registered between {ritual.ideo.memberName}s {p} and {newEngagement.otherPawn}.");
            log.Message($"{ritual.Label} opportunity expected in {GenDate.ToStringTicksToPeriod(ticksToMarriage, shortForm: true, canUseDecimalsShortForm: true)}");


            engagements.Add(new PawnRelationInfo(p, newEngagement));
            engagements.SortBy(e => e.startTicks);
            hasEngagement = true;
        }

        public virtual void Notify_EngagementEnded(Pawn pawn, Pawn otherPawn)
        {
            log.Message($"{pawn} and {otherPawn}'s engagemnt ended. Attempting to remove from {ritual.Label} obligation trigger.");
            for (int i = 0; i < engagements.Count; i++)
            {
                if (engagements[i].IsBetween(pawn, otherPawn))
                {
                    log.Message($"Found engagemnt listing for {pawn} and {otherPawn}.");
                    for (int j = 0; j < ritual.activeObligations.Count; j++)
                    {
                        RitualObligation ob = ritual.activeObligations[j];

                        if (ob.IsFor(pawn, otherPawn))
                        {
                            log.Message($"Found a {ritual.Label} obligation.");
                            ritual.RemoveObligation(ob, completed: CurrentlyMarrying(pawn, otherPawn));
                            break;
                        }
                    }
                    RemoveEngagement(i);
                    break;
                }
            }
        }

        // Remove all engagements involving this pawn.
        // If totalRemoval is set to false, we keep engagements where the other pawn is still of this ritual's ideo.
        private void RemoveEngagementsFor(Pawn p, bool totalRemoval = true)
        {
            ritual.activeObligations.RemoveAll(ob => ob.targetA == p || ob.targetB == p);

            for (int i = 0; i < engagements.Count; i++)
            {
                var (pawn, otherPawn) = engagements[i];
                bool shouldRemove = false;
                if (pawn == p)
                {
                    if (totalRemoval || otherPawn.Ideo != ritual.ideo)
                    {
                        shouldRemove = true;
                    }
                }
                else if (otherPawn == p)
                {
                    if (totalRemoval || pawn.Ideo != ritual.ideo)
                    {
                        shouldRemove = true;
                    }
                }

                if (shouldRemove)
                {
                    var obligation = GetObligationFor(pawn, otherPawn);
                    if (obligation != null)
                    {
                        ritual.RemoveObligation(obligation, completed: false);
                    }
                    // Offset the increment since we just shrank the list.
                    RemoveEngagement(i--);
                }
            }
        }

        private void RemoveEngagement(int index)
        {
            engagements.RemoveAt(index);
            NextOpportunityTick = -1;
        }

        private bool CurrentlyMarrying(Pawn pawn, Pawn otherPawn)
        {
            if (pawn.GetLord()?.LordJob is not LordJob_Ritual ritual)
            {
                return false;
            }
            if (pawn.GetLord() != otherPawn.GetLord())
            {
                return false;
            }
            if (ritual.Ritual.def != SCEPreceptDefOf.LewdWedding)
            {
                return false;
            }
            return ritual.RoleFor(pawn) is RitualRoleBetrothed && ritual.RoleFor(otherPawn) is RitualRoleBetrothed;
        }
        
        private RitualObligation GetObligationFor(Pawn pawn, Pawn otherPawn)
        {
            return ritual.activeObligations.FirstOrDefault(ob => ob.IsFor(pawn, otherPawn));
        }
        
        public override void ExposeData()
        {
            base.ExposeData();
            Scribe_Collections.Look(ref engagements, "engagements", LookMode.Deep);
        }
    }
}