import { Inject, Injectable } from '@nestjs/common';
import { Redis } from 'ioredis';
import type { MissionStateResponse } from '@fog-explorer/api-interfaces';
import { REDIS_CLIENT } from '../redis/redis.module';

const ACTIVE_MISSION_PREFIX = 'active_mission:';
const CHANNEL_MISSION_KEY = (channelId: string) => `channel_mission:${channelId}`;
const TICK_QUEUE_KEY = 'missions:tick_queue';
const LOCK_PREFIX = 'tick_lock:';
const LOCK_TTL_MS = 30_000;

@Injectable()
export class MissionStoreService {
  constructor(@Inject(REDIS_CLIENT) private readonly redis: Redis) {}

  async getActiveMission(missionId: string): Promise<MissionStateResponse | null> {
    const raw = await this.redis.get(`${ACTIVE_MISSION_PREFIX}${missionId}`);
    if (!raw) return null;
    return JSON.parse(raw) as MissionStateResponse;
  }

  async setActiveMission(state: NonNullable<MissionStateResponse>): Promise<void> {
    const missionId = state.mission.id;
    const key = `${ACTIVE_MISSION_PREFIX}${missionId}`;
    await this.redis.set(key, JSON.stringify(state));
  }

  async deleteActiveMission(missionId: string): Promise<void> {
    await this.redis.del(`${ACTIVE_MISSION_PREFIX}${missionId}`);
  }

  async getChannelMissionId(channelId: string): Promise<string | null> {
    return this.redis.get(CHANNEL_MISSION_KEY(channelId));
  }

  async setChannelMissionId(channelId: string, missionId: string): Promise<void> {
    await this.redis.set(CHANNEL_MISSION_KEY(channelId), missionId);
  }

  async clearChannelMission(channelId: string): Promise<void> {
    await this.redis.del(CHANNEL_MISSION_KEY(channelId));
  }

  async getStateForChannel(channelId: string): Promise<MissionStateResponse> {
    const missionId = await this.getChannelMissionId(channelId);
    if (!missionId) return null;
    return this.getActiveMission(missionId);
  }

  // Sorted-set tick queue — score is the nextTickAt Unix ms timestamp.
  async scheduleTick(missionId: string, nextTickAtMs: number): Promise<void> {
    await this.redis.zadd(TICK_QUEUE_KEY, nextTickAtMs, missionId);
  }

  async removeMissionFromQueue(missionId: string): Promise<void> {
    await this.redis.zrem(TICK_QUEUE_KEY, missionId);
  }

  async getDueMissionIds(nowMs: number): Promise<string[]> {
    return this.redis.zrangebyscore(TICK_QUEUE_KEY, '-inf', nowMs);
  }

  // Distributed lock — SET NX PX with a unique token.
  async acquireLock(missionId: string): Promise<string | null> {
    const token = `${process.pid}-${Date.now()}-${Math.random()}`;
    const result = await this.redis.set(
      `${LOCK_PREFIX}${missionId}`,
      token,
      'PX',
      LOCK_TTL_MS,
      'NX'
    );
    return result === 'OK' ? token : null;
  }

  async releaseLock(missionId: string, token: string): Promise<void> {
    // Atomic check-and-delete via Lua to avoid releasing another owner's lock.
    const script = `
      if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
      else
        return 0
      end
    `;
    await this.redis.eval(script, 1, `${LOCK_PREFIX}${missionId}`, token);
  }
}
