/**
 * Retention Handlers
 * Handles achievements, daily rewards, daily quests, and event claiming
 */

import type { PlayerSession } from '../game/PlayerSession.js';
import {
  getPlayerAchievements,
  getPlayerAchievementsAsync,
  loadPlayerAchievements,
  getAchievementSummary,
  handleDailyLoginCheck,
  claimDailyReward,
  getDailyRewardState,
  handleDailyQuestCheck,
  claimQuestReward,
  getActiveQuests,
  generateDailyQuests,
  formatQuestForClient,
  handleTutorialStepComplete as completeTutorialStepForPlayer,
  handleCompleteOnboarding as completeOnboardingForPlayer,
  handleTooltipSeen as recordTooltipSeen,
} from '../services/retention/index.js';
import { resolvePayloadField, resolvePayloadNumber } from './payloadHelpers.js';

interface AcknowledgePayload {
  achievementId: string;
}

interface ClaimDailyPayload {
  day?: number;
}

interface ClaimQuestPayload {
  questId: string;
}

interface ClaimEventPayload {
  eventId: string;
  timestamp?: string;
}

interface ClaimableLifeEvent {
  id: string;
  claimed?: boolean;
  claimedAt?: string;
  moneyReward?: number;
  energyReward?: number;
  diamondReward?: number;
}

// Map server categories to iOS categories
const CATEGORY_MAP: Record<string, string> = {
  'life_milestone': 'Education',
  'career': 'Career',
  'relationship': 'Relationships',
  'collection': 'Wealth',
  'secret': 'Activities',
};

export async function handleGetAchievements(
  _payload: unknown,
  session: PlayerSession
): Promise<void> {
  const player = session.player;

  try {
    // Load achievements from database (populates cache)
    await loadPlayerAchievements(player.userId);

    // Get achievements with database backing
    const achievementsData = await getPlayerAchievementsAsync(player.userId);

    // Get summary by category
    const summary = getAchievementSummary(player.userId);

    // Format achievements for iOS - combine unlocked and locked into single array
    // iOS expects: id, name, description, category, reward, requirement, unlocked, unlockedAt, progress, progressRequired, acknowledged
    const formattedAchievements = [
      ...achievementsData.unlocked.map(ach => ({
        id: ach.id || ach.key,
        name: ach.name,
        description: ach.description,
        category: CATEGORY_MAP[(ach as any).category] || 'Activities',
        reward: ach.reward,
        requirement: ach.description,
        unlocked: true,
        unlockedAt: ach.unlockedAt?.toISOString() || null,
        progress: 1,
        progressRequired: 1,
        acknowledged: true,
      })),
      ...achievementsData.locked.map(ach => ({
        id: ach.key,
        name: ach.name,
        description: ach.desc,
        category: CATEGORY_MAP[ach.category] || 'Activities',
        reward: ach.reward,
        requirement: ach.desc,
        unlocked: false,
        unlockedAt: null,
        progress: 0,
        progressRequired: 1,
        acknowledged: false,
      })),
    ];

    // Python: {'type': 'achievementsList', 'achievements': achievements}
    session.send({
      type: 'achievementsList',
      achievements: formattedAchievements,
      summary: summary,
    });
  } catch (error) {
    console.error(`Error getting achievements for player ${player.userId}:`, error);

    // Fallback to cache-only version
    const achievements = getPlayerAchievements(player.userId);

    // Format for iOS
    const formattedAchievements = [
      ...achievements.unlocked.map(ach => ({
        id: ach.id || ach.key,
        name: ach.name,
        description: ach.description,
        category: CATEGORY_MAP[(ach as any).category] || 'Activities',
        reward: ach.reward,
        requirement: ach.description,
        unlocked: true,
        unlockedAt: null,
        progress: 1,
        progressRequired: 1,
        acknowledged: true,
      })),
      ...achievements.locked.map(ach => ({
        id: ach.key,
        name: ach.name,
        description: ach.desc,
        category: CATEGORY_MAP[ach.category] || 'Activities',
        reward: ach.reward,
        requirement: ach.desc,
        unlocked: false,
        unlockedAt: null,
        progress: 0,
        progressRequired: 1,
        acknowledged: false,
      })),
    ];

    session.send({
      type: 'achievementsList',
      achievements: formattedAchievements,
    });
  }
}

export async function handleAcknowledgeAchievement(
  payload: unknown,
  session: PlayerSession
): Promise<void> {
  const achievementId = resolvePayloadField(payload, 'achievementId');
  const player = session.player;

  if (!achievementId) {
    session.send({
      type: 'error',
      message: 'Missing achievementId',
    });
    return;
  }

  console.log(`Player ${player.userId} acknowledged achievement ${achievementId}`);

  session.send({
    type: 'achievementAcknowledged',
    achievementId,
    success: true,
  });
}

export async function handleGetDailyRewards(
  _payload: unknown,
  session: PlayerSession
): Promise<void> {
  const player = session.player;

  // Use the async handler which sends the complete state
  await handleDailyLoginCheck(player.userId, (_playerId, message) => {
    session.send(message);
  });
}

export async function handleClaimDailyReward(
  payload: unknown,
  session: PlayerSession
): Promise<void> {
  const { day } = payload as ClaimDailyPayload;
  const player = session.player;

  const result = await claimDailyReward(player.userId, player);

  // Apply energy reward if applicable
  if (result.success && result.reward) {
    if (result.reward.type === 'energy') {
      player.c.energy = Math.min(100, (player.c.energy ?? 100) + result.reward.amount);
    }
    // Diamond rewards are handled inside claimDailyReward via awardDiamonds
  }

  // Spread result at root level like Python
  session.send({
    type: 'dailyRewardClaimed',
    ...result,
    day,
  });

  if (result.success) {
    session.sendPlayerObject();

    // Also send updated daily reward state
    const state = await getDailyRewardState(player.userId);
    session.send({
      type: 'dailyRewardStatus',
      ...state,
    });
  }
}

/**
 * Handle get daily quests request
 * Returns the player's current daily quests, generating new ones if needed
 */
export async function handleGetDailyQuests(
  _payload: unknown,
  session: PlayerSession
): Promise<void> {
  const player = session.player;

  try {
    // Check if player has quests for today
    let activeQuests = await getActiveQuests(player.userId);

    if (activeQuests.length === 0) {
      // Generate new quests
      activeQuests = await generateDailyQuests(player.userId);
    }

    // Format quests for client
    const formattedQuests = activeQuests.map(formatQuestForClient);

    // Calculate reset times
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);

    // Send complete state matching iOS DailyQuestsState model
    session.send({
      type: 'dailyQuestsStatus',
      quests: formattedQuests,
      lastResetDate: today.toISOString(),
      nextResetDate: tomorrow.toISOString(),
    });

    console.log(`Sent daily quests state to player ${player.userId}: ${formattedQuests.length} quests`);
  } catch (error) {
    console.error(`Error handling get daily quests for player ${player.userId}:`, error);
    session.send({
      type: 'dailyQuestsStatus',
      quests: [],
      lastResetDate: new Date().toISOString(),
      nextResetDate: new Date().toISOString(),
      error: 'Failed to load quests',
    });
  }
}

/**
 * Handle claim quest reward request
 * Validates quest completion and awards diamonds
 */
export async function handleClaimQuestReward(
  payload: unknown,
  session: PlayerSession
): Promise<void> {
  const questId = resolvePayloadField(payload, 'questId');
  const player = session.player;

  if (!questId) {
    session.send({
      type: 'questRewardClaimed',
      success: false,
      message: 'Missing questId',
      reward: null,
    });
    return;
  }

  try {
    const result = await claimQuestReward(player.userId, questId, player);

    // Spread result at root level like Python
    session.send({
      type: 'questRewardClaimed',
      ...result,
    });

    if (result.success) {
      session.sendPlayerObject();

      // Refresh quest state
      await handleDailyQuestCheck(player.userId, (_playerId, message) => {
        session.send(message);
      });
    }
  } catch (error) {
    console.error(`Error claiming quest reward for player ${player.userId}:`, error);
    session.send({
      type: 'questRewardClaimed',
      success: false,
      message: 'Failed to claim reward',
      reward: null,
    });
  }
}

export async function handleClaimEvent(payload: unknown, session: PlayerSession): Promise<void> {
  const eventId = resolvePayloadField(payload, 'eventId');
  const timestamp = payload && typeof payload === 'object'
    ? (payload as ClaimEventPayload).timestamp
    : undefined;
  const player = session.player;

  if (!eventId) {
    session.send({
      type: 'eventClaimed',
      success: false,
      eventId: eventId ?? '',
      message: 'Missing eventId',
    });
    return;
  }

  const lifeEvents = (player as unknown as { lifeEvents?: ClaimableLifeEvent[] }).lifeEvents ?? [];
  const lifeEvent = lifeEvents.find(e => e.id === eventId);
  const eventExists = player.events.has(eventId) || !!lifeEvent;
  const claimMarker = `claimed:${eventId}`;
  const alreadyClaimed = player.events.has(claimMarker) || lifeEvent?.claimed === true;

  if (!eventExists) {
    console.warn(`Player ${player.userId} attempted to claim missing event ${eventId}`);
    session.send({
      type: 'eventClaimed',
      success: false,
      eventId,
      message: 'Event not found',
    });
    return;
  }

  if (alreadyClaimed) {
    console.warn(`Player ${player.userId} attempted duplicate claim for ${eventId}`);
    session.send({
      type: 'eventClaimed',
      success: false,
      eventId,
      message: 'Event already claimed',
    });
    return;
  }

  const moneyReward = Math.max(0, lifeEvent?.moneyReward ?? 0);
  const energyReward = Math.max(0, lifeEvent?.energyReward ?? 0);
  const diamondReward = Math.max(0, lifeEvent?.diamondReward ?? 0);

  if (moneyReward > 0) {
    player.c.money = (player.c.money ?? 0) + moneyReward;
  }
  if (energyReward > 0) {
    player.c.energy = Math.min(100, (player.c.energy ?? 100) + energyReward);
  }
  if (diamondReward > 0) {
    player.c.diamonds = (player.c.diamonds ?? 0) + diamondReward;
  }

  player.events.add(claimMarker);
  if (lifeEvent) {
    lifeEvent.claimed = true;
    lifeEvent.claimedAt = timestamp ?? new Date().toISOString();
  }

  console.log(`Player ${player.userId} claimed event ${eventId} at ${timestamp}`);

  session.send({
    type: 'eventClaimed',
    success: true,
    eventId,
    timestamp: timestamp ?? new Date().toISOString(),
    rewards: {
      money: moneyReward,
      energy: energyReward,
      diamonds: diamondReward,
    },
  });

  session.sendPlayerObject();
}

export async function handleTutorialStepComplete(payload: unknown, session: PlayerSession): Promise<void> {
  const step = resolvePayloadNumber(payload, 'step');

  if (step === undefined || !Number.isInteger(step)) {
    session.send({
      type: 'error',
      message: 'Invalid tutorial step payload.',
    });
    return;
  }

  if (step > session.player.tutorialStep) {
    session.player.tutorialStep = step;
  }

  await completeTutorialStepForPlayer(
    session.player.userId,
    { step },
    (_playerId, message) => session.send(message)
  );

  if (step >= 1) {
    await session.savePlayer();
  }
}

export async function handleCompleteOnboarding(_payload: unknown, session: PlayerSession): Promise<void> {
  if (session.player.onboardingComplete) {
    session.send({
      type: 'onboardingAlreadyComplete',
      isComplete: true,
    });
    return;
  }

  await completeOnboardingForPlayer(
    session.player.userId,
    (_playerId, message) => session.send(message),
    session.player
  );

  session.player.onboardingComplete = true;
  await session.savePlayer();
}

export async function handleTooltipSeen(payload: unknown, session: PlayerSession): Promise<void> {
  const tooltipId = resolvePayloadField(payload, 'tooltipId');
  if (!tooltipId) {
    return;
  }

  recordTooltipSeen(session.player.userId, { tooltipId });
  session.send({
    type: 'tooltipSeenAcknowledged',
    success: true,
    tooltipId,
  });
}

/**
 * Handle getting daily reward state.
 * This is the primary handler for 'getDailyRewardState' command.
 */
export async function handleGetDailyRewardState(
  _payload: unknown,
  session: PlayerSession
): Promise<void> {
  const player = session.player;

  const state = await getDailyRewardState(player.userId);

  session.send({
    type: 'dailyRewardStatus',
    ...state,
  });
}
