import { BadRequestException, ConflictException, Inject, Injectable } from '@nestjs/common';
import { PlayerWalletPayload } from '../player/player.types';
import {
  getCurrentWeeklyCycle,
  getTaskCycleKey,
  getTaskDefinitions,
  NEWBIE_CYCLE_KEY,
  TaskActivityType,
  TaskDefinition,
  TaskProgressState,
  WEEKLY_COMPLETION_BONUS,
  WEEKLY_TASKS,
} from './task_catalog';
import { buildTaskBoard, TaskBoardPayload, TaskClaimResult, TaskProgressRow, TaskProgressStore } from './tasks.types';

interface IUserWalletsService {
  earn(userId: bigint, amount: number, referenceType: string, referenceId: string, note?: string): Promise<{ wallet: { balance: number } }>;
}

interface MemoryProgressRow extends TaskProgressRow {
  user_id: bigint;
  created_at: Date;
  updated_at: Date;
}

@Injectable()
export class TaskProgressMemoryService implements TaskProgressStore {
  private readonly rows = new Map<string, MemoryProgressRow>();

  constructor(@Inject('IUserWalletsService') private readonly walletService: IUserWalletsService) {}

  async getBoard(userId: bigint): Promise<TaskBoardPayload> {
    const cycle = getCurrentWeeklyCycle();
    const rows = this.ensureRows(userId, cycle.key);
    this.syncWeeklyBonus(rows);
    return buildTaskBoard(rows, cycle);
  }

  async recordActivity(userId: bigint, activity: TaskActivityType, targetId?: string): Promise<TaskBoardPayload> {
    const cycle = getCurrentWeeklyCycle();
    const rows = this.ensureRows(userId, cycle.key);
    for (const definition of getTaskDefinitions()) {
      if (definition.bonus || definition.activity !== activity) continue;
      const row = this.findRow(rows, definition, cycle.key);
      this.applyActivity(definition, row, targetId);
    }
    this.syncWeeklyBonus(rows);
    return buildTaskBoard(rows, cycle);
  }

  async claim(userId: bigint, taskId: string): Promise<TaskClaimResult> {
    const cycle = getCurrentWeeklyCycle();
    const rows = this.ensureRows(userId, cycle.key);
    this.syncWeeklyBonus(rows);
    const definition = getTaskDefinitions().find((item) => item.id === taskId);
    if (!definition) {
      throw new BadRequestException('任务不存在');
    }
    const row = this.findRow(rows, definition, cycle.key);
    if (!row.completed_at) {
      throw new BadRequestException('任务尚未完成');
    }
    if (row.claimed_at) {
      throw new ConflictException('任务奖励已领取');
    }
    const result = await this.walletService.earn(
      userId,
      definition.reward,
      'task_reward',
      `${getTaskCycleKey(definition, cycle)}:${definition.id}`,
      `任务奖励：${definition.title}`,
    );
    row.claimed_at = new Date();
    row.updated_at = new Date();
    const wallet: PlayerWalletPayload = {
      user_id: userId.toString(),
      balance: result.wallet.balance,
      currency: 'whale_coin',
    };
    return { board: buildTaskBoard(rows, cycle), wallet };
  }

  private ensureRows(userId: bigint, weeklyCycleKey: string): MemoryProgressRow[] {
    for (const definition of getTaskDefinitions()) {
      const cycleKey = definition.group === 'weekly' ? weeklyCycleKey : NEWBIE_CYCLE_KEY;
      const key = this.rowKey(userId, definition.id, cycleKey);
      if (!this.rows.has(key)) {
        const now = new Date();
        this.rows.set(key, {
          user_id: userId,
          task_id: definition.id,
          cycle_key: cycleKey,
          progress: 0,
          activity_state: {},
          completed_at: null,
          claimed_at: null,
          created_at: now,
          updated_at: now,
        });
      }
    }
    return getTaskDefinitions().map((definition) => {
      const cycleKey = definition.group === 'weekly' ? weeklyCycleKey : NEWBIE_CYCLE_KEY;
      return this.rows.get(this.rowKey(userId, definition.id, cycleKey)) as MemoryProgressRow;
    });
  }

  private findRow(rows: MemoryProgressRow[], definition: TaskDefinition, weeklyCycleKey: string): MemoryProgressRow {
    const cycleKey = definition.group === 'weekly' ? weeklyCycleKey : NEWBIE_CYCLE_KEY;
    const row = rows.find((item) => item.task_id === definition.id && item.cycle_key === cycleKey);
    if (!row) throw new Error(`任务进度缺失: ${definition.id}`);
    return row;
  }

  private applyActivity(definition: TaskDefinition, row: MemoryProgressRow, targetId?: string): void {
    if (row.completed_at) return;
    if (definition.allowed_targets && (!targetId || !definition.allowed_targets.includes(targetId))) return;
    if (definition.progress_mode === 'unique_target') {
      if (!targetId) return;
      const state = row.activity_state as TaskProgressState;
      const targets = Array.isArray(state.targets) ? state.targets.filter((item): item is string => typeof item === 'string') : [];
      if (targets.includes(targetId)) return;
      targets.push(targetId);
      row.activity_state = { ...state, targets };
      row.progress = Math.min(definition.target, targets.length);
    } else {
      row.progress = Math.min(definition.target, row.progress + 1);
    }
    if (row.progress >= definition.target) row.completed_at = new Date();
    row.updated_at = new Date();
  }

  private syncWeeklyBonus(rows: MemoryProgressRow[]): void {
    const bonus = rows.find((row) => row.task_id === WEEKLY_COMPLETION_BONUS.id);
    if (!bonus || bonus.completed_at) return;
    const complete = WEEKLY_TASKS.every((definition) => rows.some((row) => row.task_id === definition.id && row.completed_at));
    if (complete) {
      bonus.progress = 1;
      bonus.completed_at = new Date();
      bonus.updated_at = new Date();
    }
  }

  private rowKey(userId: bigint, taskId: string, cycleKey: string): string {
    return `${userId.toString()}:${taskId}:${cycleKey}`;
  }
}
