import { Injectable } from '@nestjs/common';
import { PinoLogger } from 'nestjs-pino';
import type {
  EncounterResult,
  Mission,
  MissionParticipant,
  MissionStateResponse,
} from '@fog-explorer/api-interfaces';
import {
  getEncounterById,
  getLibraryVersion,
  getRandomEncounterByTier,
  pickFlavor,
} from '@fog-explorer/encounter-library';
import { resolveEncounter } from '@fog-explorer/mission-logic';
import seedrandom = require('seedrandom');
import { GroupSynergyService } from './group-synergy.service';

const TICK_JITTER_MS = 5_000;
const TICK_BASE_INTERVAL_MS = 60_000;
const RECENT_LOG_MAX = 20;

@Injectable()
export class EncounterService {
  constructor(
    private readonly logger: PinoLogger,
    private readonly groupSynergy: GroupSynergyService,
  ) {}

  /**
   * Resolves one tick for the given mission state.
   * channelId is threaded in from the TickService for structured log correlation.
   */
  processTick(
    current: NonNullable<MissionStateResponse>,
    channelId: string | null,
  ): NonNullable<MissionStateResponse> {
    const { mission, survivors } = current;
    const tickIndex = mission.tickIndex + 1;
    const seed = buildSeed(mission.id, tickIndex);
    const rng = seedrandom(seed);

    const tier = mission.difficulty as 1 | 2 | 3;
    const encounter =
      mission.encounterLibraryVersion === getLibraryVersion()
        ? getRandomEncounterByTier(tier, rng)
        : getRandomEncounterByTier(tier, rng);

    const groupModifiers = this.groupSynergy.collectGroupModifiers(survivors);
    const newLog: EncounterResult[] = [];
    let updatedSurvivors = survivors.map((s) => ({ ...s }));
    let updatedParticipants = mission.participants.map((p) => ({ ...p }));

    for (const participant of mission.participants) {
      if (participant.state === 'sacrificed') continue;

      const survivor = survivors.find((s) => s.id === participant.survivorId);
      if (!survivor) continue;

      const augmentedPerks = this.groupSynergy.buildAugmentedPerks(
        survivor,
        groupModifiers,
      );

      const result = resolveEncounter({
        seed: `${seed}:${survivor.id}`,
        missionId: mission.id,
        tickIndex,
        difficulty: mission.difficulty,
        encounter,
        survivor: {
          id: survivor.id,
          state: survivor.state,
          stats: survivor.stats,
          perkSlots: augmentedPerks,
          hookCount: participant.hookCount,
        },
      });

      const libEncounter = getEncounterById(encounter.key);
      const flavor = libEncounter
        ? pickFlavor(libEncounter, { success: result.success, killerName: mission.killerName }, rng)
        : result.logText;

      newLog.push({ ...result, logText: flavor });

      this.logger.info({
        msg: 'tick resolved',
        missionId: mission.id,
        channelId,
        tickIndex,
        survivorId: survivor.id,
        encounterKey: encounter.key,
        success: result.success,
        seed: result.seed,
        modifiersApplied: result.modifiersApplied.length,
      });

      const stateChange = result.survivorStateChange;
      if (stateChange) {
        updatedSurvivors = updatedSurvivors.map((s) =>
          s.id === survivor.id ? { ...s, state: stateChange.to } : s,
        );
        updatedParticipants = updatedParticipants.map((p) => {
          if (p.survivorId !== survivor.id) return p;
          const hookCount =
            stateChange.to === 'downed'
              ? Math.min(2, p.hookCount + 1)
              : p.hookCount;
          return { ...p, state: stateChange.to, hookCount };
        });
      }
    }

    const updatedMission = buildUpdatedMission(
      mission,
      updatedParticipants,
      tickIndex,
    );

    const recentLog = [...newLog, ...current.recentLog].slice(
      0,
      RECENT_LOG_MAX,
    );

    return {
      mission: updatedMission,
      survivors: updatedSurvivors,
      recentLog,
    };
  }
}

function buildSeed(missionId: string, tickIndex: number): string {
  return `${missionId}:${tickIndex}`;
}

function buildUpdatedMission(
  mission: Mission,
  participants: MissionParticipant[],
  tickIndex: number,
): Mission {
  const allSacrificed = participants.every((p) => p.state === 'sacrificed');

  let status = mission.status;
  let endedAt = mission.endedAt;

  if (allSacrificed && mission.status === 'active') {
    status = 'sacrifice';
    endedAt = new Date().toISOString();
  } else if (!allSacrificed && tickIndex >= mission.durationMinutes && mission.status === 'active') {
    status = 'success';
    endedAt = new Date().toISOString();
  }

  const jitter = Math.floor(Math.random() * TICK_JITTER_MS);
  const nextTickAt = new Date(
    Date.now() + TICK_BASE_INTERVAL_MS + jitter,
  ).toISOString();

  return {
    ...mission,
    status,
    endedAt,
    participants,
    tickIndex,
    nextTickAt,
  };
}
