import { ConflictException, HttpException, HttpStatus, Inject, Injectable, Logger } from '@nestjs/common';
import { IRedisService } from '../../core/redis/redis.interface';
import { Position } from '../../core/location_broadcast_core/position.interface';
import { LocationBroadcastCore } from '../../core/location_broadcast_core/location_broadcast_core.service';
import { ReportTaskActivityDto } from './dto/report_task_activity.dto';
import { TaskActivityType } from './task_catalog';
import {
  TASK_ACTIVITY_AUDIT_STORE,
  TaskActivityAuditRecord,
  TaskActivityAuditStore,
} from './task_activity_audit.store';

interface TaskActivityRequestContext {
  clientIp?: string;
  userAgent?: string;
}

interface ResolvedActivity {
  activity: TaskActivityType;
  targetId?: string;
}

interface FacilityPoint {
  mapId: string;
  x: number;
  y: number;
  radius: number;
}

const SESSION_REQUIRED_ACTIVITIES = new Set<TaskActivityType>([
  'map_visited',
  'notice_viewed',
  'course_board_opened',
  'facility_interacted',
]);
const MAP_TASK_TARGETS: Record<string, string> = {
  whale_port: 'square',
  work_zone: 'work_zone',
  whale_cafe: 'whale_cafe',
};
const FACILITY_POINTS: Record<string, FacilityPoint[]> = {
  welcome_board: [{ mapId: 'whale_port', x: 1470, y: 408, radius: 220 }],
  notice_board: [{ mapId: 'whale_port', x: 701, y: 1529, radius: 240 }],
  course_board: [{ mapId: 'work_zone', x: 1778, y: 1366, radius: 240 }],
  npc: [
    { mapId: 'whale_port', x: 1081, y: 445, radius: 220 },
    { mapId: 'whale_port', x: 455, y: 1397, radius: 220 },
    { mapId: 'whale_cafe', x: 296, y: 221, radius: 240 },
  ],
};
const NONCE_TTL_SECONDS = 10 * 60;
const SESSION_FRESHNESS_MS = 30 * 1000;
const RATE_LIMIT_PER_MINUTE = 30;

@Injectable()
export class TaskActivityAuthorityService {
  private readonly logger = new Logger(TaskActivityAuthorityService.name);

  constructor(
    @Inject('REDIS_SERVICE') private readonly redisService: IRedisService,
    private readonly locationBroadcastCore: LocationBroadcastCore,
    @Inject(TASK_ACTIVITY_AUDIT_STORE) private readonly auditStore: TaskActivityAuditStore,
  ) {}

  async resolve(
    userId: bigint,
    dto: ReportTaskActivityDto,
    context: TaskActivityRequestContext = {},
  ): Promise<ResolvedActivity> {
    const requestedTargetId = dto.target_id?.trim() || undefined;
    const auditBase: TaskActivityAuditRecord = {
      user_id: userId,
      nonce: dto.nonce,
      activity: dto.activity,
      requested_target_id: requestedTargetId ?? null,
      resolved_target_id: null,
      accepted: false,
      reason: 'pending',
      session_id: null,
      map_id: null,
      position_x: null,
      position_y: null,
      client_ip: context.clientIp?.slice(0, 64) || null,
      user_agent: context.userAgent?.slice(0, 255) || null,
      client_occurred_at: dto.occurred_at ? new Date(dto.occurred_at) : null,
    };

    try {
      await this.enforceRateLimit(userId);
      const nonceAccepted = await this.redisService.setIfAbsent(
        `tasks:activity:nonce:${userId.toString()}:${dto.nonce}`,
        '1',
        NONCE_TTL_SECONDS,
      );
      if (!nonceAccepted) {
        throw new ConflictException('任务活动 nonce 已使用');
      }

      let position: Position | null = null;
      let sessionId: string | null = null;
      if (SESSION_REQUIRED_ACTIVITIES.has(dto.activity)) {
        [sessionId, position] = await Promise.all([
          this.redisService.get(`user:${userId.toString()}:session`),
          this.locationBroadcastCore.getUserPosition(userId.toString()),
        ]);
        auditBase.session_id = sessionId;
        auditBase.map_id = position?.mapId ?? null;
        auditBase.position_x = position?.x ?? null;
        auditBase.position_y = position?.y ?? null;
        this.assertActiveSession(sessionId, position);
      }

      const resolved = this.resolveActivity(dto.activity, requestedTargetId, position);
      await this.appendAudit({
        ...auditBase,
        resolved_target_id: resolved.targetId ?? null,
        accepted: true,
        reason: 'accepted',
      });
      return resolved;
    } catch (error) {
      await this.appendAudit({
        ...auditBase,
        reason: error instanceof Error ? error.message.slice(0, 120) : 'unknown_error',
      });
      throw error;
    }
  }

  private async enforceRateLimit(userId: bigint): Promise<void> {
    const minuteBucket = Math.floor(Date.now() / 60000);
    const key = `tasks:activity:rate:${userId.toString()}:${minuteBucket}`;
    const count = await this.redisService.incr(key);
    if (count === 1) {
      await this.redisService.expire(key, 120);
    }
    if (count > RATE_LIMIT_PER_MINUTE) {
      throw new HttpException('任务活动上报过于频繁', HttpStatus.TOO_MANY_REQUESTS);
    }
  }

  private assertActiveSession(sessionId: string | null, position: Position | null): asserts position is Position {
    if (!sessionId || !position) {
      throw new ConflictException('游戏会话尚未就绪');
    }
    if (!Number.isFinite(position.timestamp) || Date.now() - position.timestamp > SESSION_FRESHNESS_MS) {
      throw new ConflictException('游戏会话位置已过期');
    }
  }

  private resolveActivity(
    activity: TaskActivityType,
    requestedTargetId: string | undefined,
    position: Position | null,
  ): ResolvedActivity {
    if (activity === 'map_visited') {
      const targetId = MAP_TASK_TARGETS[position?.mapId ?? ''];
      if (!targetId) {
        throw new ConflictException('当前地图不计入任务进度');
      }
      if (requestedTargetId && requestedTargetId !== targetId) {
        throw new ConflictException('地图活动与当前会话不一致');
      }
      return { activity, targetId };
    }

    if (activity === 'notice_viewed') {
      this.assertNearFacility(position, 'notice_board');
      return { activity, targetId: 'notice_board' };
    }

    if (activity === 'course_board_opened') {
      this.assertNearFacility(position, 'course_board');
      return { activity, targetId: 'course_board' };
    }

    if (activity === 'facility_interacted') {
      if (!requestedTargetId || !FACILITY_POINTS[requestedTargetId]) {
        throw new ConflictException('设施活动目标无效');
      }
      this.assertNearFacility(position, requestedTargetId);
      return { activity, targetId: requestedTargetId };
    }

    return { activity, targetId: requestedTargetId };
  }

  private assertNearFacility(position: Position | null, facilityId: string): void {
    if (!position) {
      throw new ConflictException('游戏会话尚未就绪');
    }
    const matched = FACILITY_POINTS[facilityId].some((point) => {
      if (point.mapId !== position.mapId) return false;
      const deltaX = position.x - point.x;
      const deltaY = position.y - point.y;
      return deltaX * deltaX + deltaY * deltaY <= point.radius * point.radius;
    });
    if (!matched) {
      throw new ConflictException('玩家不在目标设施交互范围内');
    }
  }

  private async appendAudit(record: TaskActivityAuditRecord): Promise<void> {
    try {
      await this.auditStore.append(record);
      this.logger.log({
        operation: 'task_activity',
        userId: record.user_id.toString(),
        activity: record.activity,
        accepted: record.accepted,
        reason: record.reason,
        mapId: record.map_id,
      });
    } catch (error) {
      this.logger.error('任务活动审计写入失败', error instanceof Error ? error.stack : String(error));
    }
  }
}
