import { BadGatewayException, Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import axios from 'axios';
import {
  DatawhaleCommitStats,
  DatawhaleHonorRankingPayload,
  DatawhaleMemberRow,
  DatawhaleWeeklyCommitsPayload,
  RankingCategory,
  RankingCategoryId,
  RankingEntry,
  RankingUser,
} from './rankings.types';

const DATAWHALE_MEMBERS_URL = 'https://mv.datawhale.cc/data/members.json';
const DATAWHALE_WEEKLY_COMMITS_URL = 'https://mv.datawhale.cc/data/commits_weekly.json';
const DATAWHALE_ASSET_BASE_URL = 'https://mv.datawhale.cc/';
const DEFAULT_CATEGORY: RankingCategoryId = 'weekly_commits';
const DEFAULT_LIMIT = 10;

const CATEGORIES: RankingCategory[] = [
  {
    id: 'weekly_commits',
    label: '一周卷王',
    title: '一周卷王',
    description: '近 7 天 commit 数 + 连续性、多仓库、质量奖励',
    icon: '🔥',
  },
  {
    id: 'night_owl',
    label: '夜猫榜',
    title: '夜猫榜',
    description: '深夜提交数量与深夜活跃比例排行',
    icon: '🌙',
  },
  {
    id: 'popularity',
    label: '人气王',
    title: '人气王',
    description: 'Followers 与组织仓库 Stars 的综合影响力',
    icon: '👑',
  },
  {
    id: 'productive',
    label: '多产榜',
    title: '多产榜',
    description: '参与 Datawhale 组织仓库数量排行',
    icon: '🏆',
  },
  {
    id: 'social',
    label: '社交达人',
    title: '社交达人',
    description: 'GitHub Following 数量排行',
    icon: '💬',
  },
  {
    id: 'rising',
    label: '新星榜',
    title: '新星榜',
    description: '按仓库数量归一后的潜力新星排行',
    icon: '🌠',
  },
  {
    id: 'comprehensive',
    label: '综合实力',
    title: '综合实力',
    description: 'Stars、Followers、仓库数、社交和贡献数综合评分',
    icon: '🌟',
  },
];

@Injectable()
export class RankingsService implements OnModuleInit {
  private readonly logger = new Logger(RankingsService.name);
  private members: DatawhaleMemberRow[] = [];
  private weeklyCommits: DatawhaleWeeklyCommitsPayload | null = null;
  private syncedAt: Date | null = null;
  private syncing: Promise<void> | null = null;

  async onModuleInit(): Promise<void> {
    this.syncNow().catch(error => {
      this.logger.warn(`Datawhale荣誉榜启动同步失败：${this.errorMessage(error)}`);
    });
  }

  @Cron('15 3 * * *')
  async syncDaily(): Promise<void> {
    await this.syncNow();
  }

  async getDatawhaleHonorRanking(
    category: RankingCategoryId = DEFAULT_CATEGORY,
    limit: number = DEFAULT_LIMIT,
    refresh = false,
  ): Promise<DatawhaleHonorRankingPayload> {
    if (refresh || this.members.length === 0) {
      await this.syncNow();
    }
    return this.getCachedPayload(category, limit);
  }

  async syncNow(): Promise<DatawhaleHonorRankingPayload> {
    if (this.syncing) {
      await this.syncing;
      return this.getCachedPayload(DEFAULT_CATEGORY, DEFAULT_LIMIT);
    }

    this.syncing = this.fetchAndReplace();
    try {
      await this.syncing;
    } finally {
      this.syncing = null;
    }
    return this.getCachedPayload(DEFAULT_CATEGORY, DEFAULT_LIMIT);
  }

  private getCachedPayload(
    category: RankingCategoryId,
    limit: number,
  ): DatawhaleHonorRankingPayload {
    const normalizedCategory = this.normalizeCategory(category);
    const normalizedLimit = this.normalizeLimit(limit);
    const entries = this.buildEntries(normalizedCategory, normalizedLimit);
    return {
      source: 'datawhale-members-visualization',
      activeCategory: normalizedCategory,
      categories: CATEGORIES,
      topRankers: entries.slice(0, 3),
      rankers: entries.slice(3, normalizedLimit),
      myRank: {
        rank: null,
        score: 0,
        reward: 0,
      },
      total: entries.length,
      syncedAt: this.syncedAt ? this.syncedAt.toISOString() : null,
      sourceUpdatedAt: this.weeklyCommits?.update_time ?? null,
    };
  }

  private async fetchAndReplace(): Promise<void> {
    try {
      const [membersResponse, commitsResponse] = await Promise.all([
        axios.get(DATAWHALE_MEMBERS_URL, { timeout: 15000 }),
        axios.get(DATAWHALE_WEEKLY_COMMITS_URL, { timeout: 15000 }),
      ]);

      if (!Array.isArray(membersResponse.data)) {
        throw new BadGatewayException('Datawhale成员接口返回格式异常');
      }
      const commitsPayload = commitsResponse.data as DatawhaleWeeklyCommitsPayload;
      if (!commitsPayload || typeof commitsPayload !== 'object' || !commitsPayload.user_commits) {
        throw new BadGatewayException('Datawhale周贡献接口返回格式异常');
      }

      this.members = membersResponse.data as DatawhaleMemberRow[];
      this.weeklyCommits = commitsPayload;
      this.syncedAt = new Date();
      this.logger.log(`Datawhale荣誉榜同步完成，共 ${this.members.length} 位成员`);
    } catch (error) {
      if (this.members.length > 0) {
        this.logger.warn(`Datawhale荣誉榜同步失败，继续使用缓存：${this.errorMessage(error)}`);
        return;
      }
      throw error;
    }
  }

  private buildEntries(category: RankingCategoryId, limit: number): RankingEntry[] {
    const users = category === 'weekly_commits' || category === 'night_owl'
      ? this.buildCommitDrivenUsers(category)
      : this.members.map(member => this.toRankingUser(member, category));

    return users
      .filter(user => user.score > 0)
      .sort((a, b) => b.score - a.score)
      .slice(0, limit)
      .map((user, index) => ({
        ...user,
        rank: index + 1,
      }));
  }

  private buildCommitDrivenUsers(category: RankingCategoryId): RankingUser[] {
    const memberById = new Map(
      this.members
        .map(member => [this.cleanString(member.id), member] as const)
        .filter(([id]) => id.length > 0),
    );
    const userCommits = this.weeklyCommits?.user_commits ?? {};

    return Object.entries(userCommits).map(([rawId, commits]) => {
      const id = this.cleanString(rawId);
      const member = memberById.get(id) ?? { id };
      return this.toRankingUser(member, category, commits);
    });
  }

  private toRankingUser(
    member: DatawhaleMemberRow,
    category: RankingCategoryId,
    commitsOverride?: DatawhaleCommitStats,
  ): RankingUser {
    const id = this.cleanString(member.id);
    const commits = commitsOverride ?? this.weeklyCommits?.user_commits?.[id] ?? {};
    const score = this.scoreMember(member, commits, category);
    const domains = this.domainList(member);
    return {
      id,
      name: this.displayName(member),
      avatarText: this.avatarText(member),
      avatarUrl: this.avatarUrl(member),
      githubUrl: this.githubUrl(member),
      domain: domains[0] ?? '',
      domains,
      location: this.cleanString(member.location),
      score,
      scoreLabel: this.scoreLabel(member, commits, category),
      contrib: this.numberValue(member.org_total_contributions) || this.numberValue(commits.total_commits),
      answers: this.numberValue(commits.repo_count) || this.numberValue(member.org_repos_count),
      likes: this.numberValue(member.org_total_stars),
      reward: this.rewardForScore(score),
    };
  }

  private scoreMember(
    member: DatawhaleMemberRow,
    commits: DatawhaleCommitStats,
    category: RankingCategoryId,
  ): number {
    switch (category) {
      case 'weekly_commits':
        return this.weeklyCommitScore(commits);
      case 'night_owl':
        return this.nightOwlScore(commits);
      case 'popularity':
        return Math.round(
          this.numberValue(member.followers ?? member.followers_count) * 0.6 +
          this.numberValue(member.org_total_stars) * 0.4,
        );
      case 'productive':
        return this.numberValue(member.org_repos_count);
      case 'social':
        return this.numberValue(member.following);
      case 'rising': {
        const repoCount = Math.max(this.numberValue(member.org_repos_count), 1);
        const bonus = repoCount < 5 ? 1.5 : 1.0;
        return Math.round((this.numberValue(member.followers ?? member.followers_count) + this.numberValue(member.org_total_stars)) / repoCount * bonus);
      }
      case 'comprehensive':
        return Math.round(
          this.numberValue(member.org_total_stars) * 0.3 +
          this.numberValue(member.followers ?? member.followers_count) * 0.25 +
          this.numberValue(member.org_repos_count) * 0.2 +
          this.numberValue(member.following) * 0.15 +
          this.numberValue(member.org_total_contributions) * 0.1,
        );
      default:
        return 0;
    }
  }

  private weeklyCommitScore(commits: DatawhaleCommitStats): number {
    let score = this.numberValue(commits.total_commits);
    const activeDays = this.numberValue(commits.active_days);
    if (activeDays >= 7) {
      score += 10;
    } else if (activeDays >= 5) {
      score += 5;
    } else if (activeDays >= 3) {
      score += 2;
    }

    const repoCount = this.numberValue(commits.repo_count);
    if (repoCount >= 5) {
      score += 5;
    } else if (repoCount >= 3) {
      score += 3;
    } else if (repoCount >= 2) {
      score += 1;
    }

    const avgCommitsPerDay = this.numberValue(commits.avg_commits_per_day);
    if (avgCommitsPerDay >= 5) {
      score += 8;
    } else if (avgCommitsPerDay >= 3) {
      score += 5;
    } else if (avgCommitsPerDay >= 2) {
      score += 2;
    }
    return Math.round(score);
  }

  private nightOwlScore(commits: DatawhaleCommitStats): number {
    let score = this.numberValue(commits.night_owl_commits) * 2;
    const percentage = this.numberValue(commits.night_owl_percentage);
    if (percentage >= 50) {
      score += 10;
    } else if (percentage >= 30) {
      score += 5;
    } else if (percentage >= 20) {
      score += 2;
    }
    const activeDays = this.numberValue(commits.active_days);
    if (activeDays >= 5) {
      score += 8;
    } else if (activeDays >= 3) {
      score += 4;
    }
    const repoCount = this.numberValue(commits.repo_count);
    if (repoCount >= 3) {
      score += 3;
    } else if (repoCount >= 2) {
      score += 1;
    }
    return Math.round(score);
  }

  private scoreLabel(
    member: DatawhaleMemberRow,
    commits: DatawhaleCommitStats,
    category: RankingCategoryId,
  ): string {
    switch (category) {
      case 'weekly_commits':
        return `${this.numberValue(commits.total_commits)} commits`;
      case 'night_owl':
        return `${this.numberValue(commits.night_owl_commits)} 深夜`;
      case 'popularity':
        return `${this.numberValue(member.followers ?? member.followers_count)} followers`;
      case 'productive':
        return `${this.numberValue(member.org_repos_count)} 仓库`;
      case 'social':
        return `${this.numberValue(member.following)} following`;
      case 'rising':
        return '活跃度';
      case 'comprehensive':
        return '综合分';
      default:
        return '分数';
    }
  }

  private domainList(member: DatawhaleMemberRow): string[] {
    const primaryDomain = this.cleanString(member.primary_domain);
    const domains = this.cleanString(member.domain)
      .split(';')
      .map(domain => this.cleanString(domain))
      .filter(Boolean);
    const result = primaryDomain ? [primaryDomain, ...domains] : domains;
    return [...new Set(result)].slice(0, 3);
  }

  private displayName(member: DatawhaleMemberRow): string {
    const name = this.cleanString(member.name);
    if (name && !['null', 'undefined', 'none'].includes(name.toLowerCase())) {
      return name;
    }
    return this.cleanString(member.id) || '未知用户';
  }

  private avatarText(member: DatawhaleMemberRow): string {
    const name = this.displayName(member);
    return name.length > 0 ? name.slice(0, 1).toUpperCase() : '鲸';
  }

  private avatarUrl(member: DatawhaleMemberRow): string {
    const avatar = this.cleanString(member.avatar);
    if (!avatar) {
      return '';
    }
    if (avatar.startsWith('http://') || avatar.startsWith('https://')) {
      return avatar;
    }
    if (avatar.startsWith('/')) {
      return `${DATAWHALE_ASSET_BASE_URL.replace(/\/$/, '')}${avatar}`;
    }
    return `${DATAWHALE_ASSET_BASE_URL}${avatar}`;
  }

  private githubUrl(member: DatawhaleMemberRow): string {
    const github = this.cleanString(member.github);
    if (github.startsWith('http')) {
      return github;
    }
    const id = this.cleanString(member.id);
    return id ? `https://github.com/${id}` : '';
  }

  private normalizeCategory(category: RankingCategoryId): RankingCategoryId {
    return CATEGORIES.some(item => item.id === category) ? category : DEFAULT_CATEGORY;
  }

  private normalizeLimit(limit: number): number {
    if (!Number.isFinite(limit)) {
      return DEFAULT_LIMIT;
    }
    return Math.min(Math.max(Math.floor(limit), 3), DEFAULT_LIMIT);
  }

  private rewardForScore(score: number): number {
    if (score >= 1000) {
      return 80;
    }
    if (score >= 500) {
      return 60;
    }
    if (score >= 120) {
      return 50;
    }
    if (score >= 50) {
      return 40;
    }
    if (score >= 20) {
      return 30;
    }
    return 20;
  }

  private numberValue(value: unknown): number {
    const parsed = Number(value || 0);
    return Number.isFinite(parsed) ? parsed : 0;
  }

  private cleanString(value: unknown): string {
    return String(value ?? '').trim();
  }

  private errorMessage(error: unknown): string {
    if (error instanceof Error) {
      return error.message;
    }
    return String(error);
  }
}
