import { BadRequestException, ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, In, Repository } from 'typeorm';
import { UserWalletsService } from '../../core/db/user_wallets/user_wallets.service';
import {
  getCurrentWeeklyCycle,
  getTaskCycleKey,
  getTaskDefinitions,
  NEWBIE_CYCLE_KEY,
  TaskActivityType,
  TaskDefinition,
  TaskProgressState,
  WEEKLY_COMPLETION_BONUS,
  WEEKLY_TASKS,
  WeeklyCycle,
} from './task_catalog';
import { PlayerTaskProgress } from './player_task_progress.entity';
import { buildTaskBoard, TaskBoardPayload, TaskClaimResult, TaskProgressRow, TaskProgressStore } from './tasks.types';

@Injectable()
export class TaskProgressDatabaseService implements TaskProgressStore {
  constructor(
    @InjectRepository(PlayerTaskProgress) private readonly progressRepository: Repository<PlayerTaskProgress>,
    private readonly dataSource: DataSource,
    private readonly walletService: UserWalletsService,
  ) {}

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

  async recordActivity(userId: bigint, activity: TaskActivityType, targetId?: string): Promise<TaskBoardPayload> {
    const cycle = getCurrentWeeklyCycle();
    return await this.dataSource.transaction(async (manager) => {
      const repository = manager.getRepository(PlayerTaskProgress);
      await this.ensureRows(repository, userId, cycle);
      const rows = await this.findRows(repository, userId, cycle, true);
      for (const definition of getTaskDefinitions()) {
        if (definition.bonus || definition.activity !== activity) continue;
        const row = this.findRow(rows, definition, cycle);
        this.applyActivity(definition, row, targetId);
      }
      await repository.save(rows);
      await this.syncWeeklyBonus(repository, rows);
      return buildTaskBoard(rows, cycle);
    });
  }

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

  private async ensureRows(repository: Repository<PlayerTaskProgress>, userId: bigint, cycle: WeeklyCycle): Promise<void> {
    const values = getTaskDefinitions().map((definition) => ({
      user_id: userId,
      task_id: definition.id,
      cycle_key: getTaskCycleKey(definition, cycle),
      progress: 0,
      activity_state: {},
      completed_at: null,
      claimed_at: null,
    }));
    await repository.createQueryBuilder().insert().values(values).orIgnore().execute();
  }

  private async findRows(repository: Repository<PlayerTaskProgress>, userId: bigint, cycle: WeeklyCycle, lock = false): Promise<PlayerTaskProgress[]> {
    return await repository.find({
      where: { user_id: userId, cycle_key: In([cycle.key, NEWBIE_CYCLE_KEY]) },
      ...(lock ? { lock: { mode: 'pessimistic_write' as const } } : {}),
    });
  }

  private findRow(rows: PlayerTaskProgress[], definition: TaskDefinition, cycle: WeeklyCycle): PlayerTaskProgress {
    const cycleKey = getTaskCycleKey(definition, cycle);
    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: PlayerTaskProgress, 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();
  }

  private async syncWeeklyBonus(repository: Repository<PlayerTaskProgress>, rows: PlayerTaskProgress[]): Promise<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();
      await repository.save(bonus);
    }
  }
}
