import type { Role } from './stage';
import type { VersionMember, VersionMemberCandidate } from './version-members';
import type { DevTask } from './dev-task';
import { STATUS_PROGRESS, getEstimateHours } from './dev-task';
import type { TestCase } from './test-case';
import { TEST_CASE_STATUS_PROGRESS, getTestCaseEstimateHours } from './test-case';
import type { Bug, BugSeverity } from './bug';
import type { OvertimeRecord } from './overtime';
import type { Effort, Requirement } from './requirement';
import type { TaskCategory } from './task-category';

export type RecommendableRole = 'frontend' | 'backend' | 'testing';
export type RecommendationConfidence = 'high' | 'medium' | 'low';

export interface HistoricalMemberStats {
  name: string;
  projectParticipationCount?: number;
  deliveredTaskCount?: number;
  severityWeightedBugCount?: number;
  delayRate?: number;
}

export interface MemberRecommendationMetrics {
  activeTaskCount: number;
  activeBugCount: number;
  deadlineConflictCount: number;
  remainingHours: number;
  recentOvertimeHours: number;
  projectParticipationCount?: number;
  bugRate?: number;
  availableInDays: number;
}

export interface MemberRecommendationItem {
  name: string;
  role: RecommendableRole;
  score: number;
  confidence: RecommendationConfidence;
  reasons: string[];
  warnings: string[];
  metrics: MemberRecommendationMetrics;
}

export interface MemberRecommendationGroup {
  role: RecommendableRole;
  requiredCount: number;
  currentCount: number;
  missingCount: number;
  scopeHours: number;
  items: MemberRecommendationItem[];
}

export interface MemberRecommendationInput {
  candidates: VersionMemberCandidate[];
  currentMembers: VersionMember[];
  devTasks: DevTask[];
  testCases: TestCase[];
  bugs: Bug[];
  overtimeRecords?: OvertimeRecord[];
  versionDeadline?: string | null;
  historicalStats?: HistoricalMemberStats[];
  scopeRequirements?: Requirement[];
  scopeDevTasks?: DevTask[];
  scopeTestCases?: TestCase[];
  taskCategories?: TaskCategory[];
  now?: Date;
}

const ROLE_ORDER: RecommendableRole[] = ['frontend', 'backend', 'testing'];
const BUG_SEVERITY_WEIGHT: Record<BugSeverity, number> = {
  critical: 4,
  major: 3,
  minor: 1.5,
  trivial: 0.5,
};
const AI_ASSISTED_ROLE_CAPACITY_HOURS: Record<RecommendableRole, number> = {
  frontend: 32,
  backend: 32,
  testing: 40,
};
const REQUIREMENT_AI_SCOPE_HOURS: Record<Effort, Record<RecommendableRole, number>> = {
  S: { frontend: 4, backend: 4, testing: 3 },
  M: { frontend: 8, backend: 8, testing: 5 },
  L: { frontend: 16, backend: 16, testing: 8 },
  XL: { frontend: 24, backend: 24, testing: 12 },
};

function clampScore(score: number): number {
  return Math.max(0, Math.min(100, Math.round(score)));
}

function roundTenth(value: number): number {
  return Math.round(value * 10) / 10;
}

function isTerminalDevTask(task: DevTask): boolean {
  return task.status === 'submitted';
}

function isTerminalTestCase(testCase: TestCase): boolean {
  return testCase.status === 'passed' || testCase.status === 'failed' || testCase.status === 'blocked';
}

function isTerminalBug(bug: Bug): boolean {
  return bug.status === 'closed' || bug.status === 'rejected';
}

function getDevTaskRemainingHours(task: DevTask): number {
  const estimate = getEstimateHours(task);
  if (estimate <= 0) return 2;
  const progress = STATUS_PROGRESS[task.status] ?? 0;
  return Math.max(0, estimate * (100 - progress) / 100);
}

function getTestCaseRemainingHours(testCase: TestCase): number {
  const estimate = getTestCaseEstimateHours(testCase);
  if (estimate <= 0) return testCase.status === 'running' ? 0.75 : 1.5;
  const progress = TEST_CASE_STATUS_PROGRESS[testCase.status] ?? 0;
  return Math.max(0, estimate * (100 - progress) / 100);
}

function getBugRemainingHours(bug: Bug): number {
  if (typeof bug.estimateHours === 'number' && bug.estimateHours > 0) return bug.estimateHours;
  if (typeof bug.aiEstimateHours === 'number' && bug.aiEstimateHours > 0) return bug.aiEstimateHours;
  return Math.max(1, BUG_SEVERITY_WEIGHT[bug.severity] ?? 1);
}

function isAfterDeadline(value: string | undefined, deadline: string | null | undefined): boolean {
  if (!value || !deadline) return false;
  const valueMs = new Date(value).getTime();
  const deadlineMs = new Date(deadline).getTime();
  return Number.isFinite(valueMs) && Number.isFinite(deadlineMs) && valueMs > deadlineMs;
}

function inferRecommendableRole(candidate: VersionMemberCandidate): RecommendableRole | undefined {
  const departmentName = candidate.departmentName?.toLowerCase() ?? '';
  if (!departmentName) return undefined;
  if (departmentName.includes('测试') || departmentName.includes('质量') || departmentName.includes('qa')) return 'testing';
  if (departmentName.includes('后端') || departmentName.includes('backend') || departmentName.includes('server')) return 'backend';
  if (departmentName.includes('前端') || departmentName.includes('frontend') || departmentName.includes('web') || departmentName.includes('client')) return 'frontend';
  return undefined;
}

function getConfidence(candidate: VersionMemberCandidate, stats: HistoricalMemberStats | undefined, hasCurrentActivity: boolean): RecommendationConfidence {
  const hasHistory = Boolean(
    stats &&
    (
      typeof stats.projectParticipationCount === 'number' ||
      typeof stats.deliveredTaskCount === 'number' ||
      typeof stats.severityWeightedBugCount === 'number' ||
      typeof stats.delayRate === 'number'
    ),
  );
  if (hasHistory) return 'high';
  if (candidate.departmentName || hasCurrentActivity) return 'medium';
  return 'low';
}

function getHistoricalBugRate(stats: HistoricalMemberStats | undefined): number | undefined {
  if (!stats?.deliveredTaskCount || stats.deliveredTaskCount <= 0) return undefined;
  if (typeof stats.severityWeightedBugCount !== 'number') return undefined;
  return roundTenth(stats.severityWeightedBugCount / stats.deliveredTaskCount);
}

function hasScopedDemandInput(input: MemberRecommendationInput): boolean {
  return Boolean(input.scopeRequirements || input.scopeDevTasks || input.scopeTestCases);
}

function countCurrentMembersByRole(currentMembers: VersionMember[]): Record<RecommendableRole, number> {
  const counts: Record<RecommendableRole, number> = { frontend: 0, backend: 0, testing: 0 };
  for (const member of currentMembers) {
    if (member.role === 'frontend' || member.role === 'backend' || member.role === 'testing') {
      counts[member.role] += 1;
    }
  }
  return counts;
}

function getRequirementScopeHours(requirements: Requirement[]): Record<RecommendableRole, number> {
  const hours: Record<RecommendableRole, number> = { frontend: 0, backend: 0, testing: 0 };
  for (const requirement of requirements) {
    const scope = REQUIREMENT_AI_SCOPE_HOURS[requirement.effort] ?? REQUIREMENT_AI_SCOPE_HOURS.M;
    hours.frontend += scope.frontend;
    hours.backend += scope.backend;
    hours.testing += scope.testing;
  }
  return hours;
}

function inferDevTaskScopeRole(task: DevTask, categories: TaskCategory[]): RecommendableRole | undefined {
  const category = categories.find((item) => item.id === task.categoryId);
  const text = `${task.categoryId} ${category?.code ?? ''} ${category?.name ?? ''}`.toLowerCase();
  if (text.includes('frontend') || text.includes('前端') || text.includes('web') || text.includes('client')) return 'frontend';
  if (
    text.includes('backend') ||
    text.includes('server') ||
    text.includes('api') ||
    text.includes('database') ||
    text.includes('db') ||
    text.includes('后端') ||
    text.includes('接口') ||
    text.includes('数据库')
  ) return 'backend';
  return undefined;
}

function getScopedDevTaskHours(devTasks: DevTask[], categories: TaskCategory[]): Pick<Record<RecommendableRole, number>, 'frontend' | 'backend'> {
  const hours = { frontend: 0, backend: 0 };
  for (const task of devTasks) {
    const estimate = Math.max(getEstimateHours(task), 2);
    const role = inferDevTaskScopeRole(task, categories);
    if (role === 'frontend' || role === 'backend') {
      hours[role] += estimate;
    } else {
      hours.frontend += estimate / 2;
      hours.backend += estimate / 2;
    }
  }
  return hours;
}

function getScopedTestCaseHours(testCases: TestCase[]): number {
  return testCases.reduce((sum, testCase) => sum + Math.max(getTestCaseEstimateHours(testCase), 1), 0);
}

function getRoleRecommendationDemand(input: MemberRecommendationInput, scoped: boolean): Record<RecommendableRole, Omit<MemberRecommendationGroup, 'items'>> {
  const currentCounts = countCurrentMembersByRole(input.currentMembers);
  const hours: Record<RecommendableRole, number> = { frontend: 0, backend: 0, testing: 0 };
  const scopeRequirements = input.scopeRequirements ?? [];
  const scopeDevTasks = input.scopeDevTasks ?? [];
  const scopeTestCases = input.scopeTestCases ?? [];
  const taskCategories = input.taskCategories ?? [];
  const requirementHours = getRequirementScopeHours(scopeRequirements);

  if (scopeDevTasks.length > 0) {
    const devHours = getScopedDevTaskHours(scopeDevTasks, taskCategories);
    hours.frontend = devHours.frontend;
    hours.backend = devHours.backend;
  } else if (scopeRequirements.length > 0) {
    hours.frontend = requirementHours.frontend;
    hours.backend = requirementHours.backend;
  }

  if (scopeTestCases.length > 0) {
    hours.testing = getScopedTestCaseHours(scopeTestCases);
  } else if (scopeRequirements.length > 0) {
    hours.testing = requirementHours.testing;
  }

  return ROLE_ORDER.reduce((acc, role) => {
    const requiredCount = scoped && hours[role] > 0
      ? Math.max(1, Math.ceil(hours[role] / AI_ASSISTED_ROLE_CAPACITY_HOURS[role]))
      : 0;
    const currentCount = currentCounts[role];
    acc[role] = {
      role,
      requiredCount,
      currentCount,
      missingCount: scoped ? Math.max(0, requiredCount - currentCount) : 0,
      scopeHours: roundTenth(hours[role]),
    };
    return acc;
  }, {} as Record<RecommendableRole, Omit<MemberRecommendationGroup, 'items'>>);
}

export function recommendVersionMembers(input: MemberRecommendationInput): MemberRecommendationGroup[] {
  const currentMemberNames = new Set(input.currentMembers.map((member) => member.name));
  const statsByName = new Map((input.historicalStats ?? []).map((stats) => [stats.name, stats]));
  const deadline = input.versionDeadline;
  const scoped = hasScopedDemandInput(input);
  const demandByRole = getRoleRecommendationDemand(input, scoped);

  const items = input.candidates
    .filter((candidate) => !currentMemberNames.has(candidate.name))
    .map((candidate): MemberRecommendationItem | null => {
      const role = inferRecommendableRole(candidate);
      if (!role) return null;

      const activeDevTasks = input.devTasks.filter((task) => task.assigneeId === candidate.name && !isTerminalDevTask(task));
      const activeTestCases = input.testCases.filter((testCase) => testCase.assigneeId === candidate.name && !isTerminalTestCase(testCase));
      const activeBugs = input.bugs.filter((bug) => bug.assigneeId === candidate.name && !isTerminalBug(bug));
      const activeTaskCount = activeDevTasks.length + activeTestCases.length;
      const activeBugCount = activeBugs.length;
      const deadlineConflictCount = activeDevTasks.filter((task) => isAfterDeadline(task.expectedEndAt, deadline)).length;
      const remainingHours = roundTenth(
        activeDevTasks.reduce((sum, task) => sum + getDevTaskRemainingHours(task), 0) +
        activeTestCases.reduce((sum, testCase) => sum + getTestCaseRemainingHours(testCase), 0) +
        activeBugs.reduce((sum, bug) => sum + getBugRemainingHours(bug), 0),
      );
      const activeBugWeight = activeBugs.reduce((sum, bug) => sum + (BUG_SEVERITY_WEIGHT[bug.severity] ?? 1), 0);
      const recentOvertimeHours = roundTenth(
        (input.overtimeRecords ?? [])
          .filter((record) => record.person === candidate.name)
          .reduce((sum, record) => sum + record.duration, 0),
      );
      const stats = statsByName.get(candidate.name);
      const projectParticipationCount = stats?.projectParticipationCount;
      const bugRate = getHistoricalBugRate(stats);
      const confidence = getConfidence(candidate, stats, activeTaskCount > 0 || activeBugCount > 0 || recentOvertimeHours > 0);

      const reasons: string[] = [`部门匹配${role === 'frontend' ? '前端' : role === 'backend' ? '后端' : '测试'}角色`];
      const warnings: string[] = [];
      let score = 65;

      if (activeTaskCount === 0 && activeBugCount === 0) {
        score += 18;
        reasons.push('当前无进行中任务，空闲度高');
      } else {
        score -= Math.min(35, activeTaskCount * 6 + activeBugCount * 5 + remainingHours * 1.2);
        reasons.push(`当前 ${activeTaskCount + activeBugCount} 个进行中事项，剩余约 ${remainingHours}h`);
      }

      if (deadlineConflictCount > 0) {
        score -= 20;
        warnings.push(`${deadlineConflictCount} 个任务预计截止晚于当前版本`);
      }

      if (activeBugCount > 0) {
        score -= Math.min(15, activeBugWeight * 3);
        warnings.push(`当前有 ${activeBugCount} 个未关闭 Bug`);
      }

      if (typeof projectParticipationCount === 'number' && projectParticipationCount > 0) {
        score += Math.min(15, projectParticipationCount * 3);
        reasons.push(`历史参与项目 ${projectParticipationCount} 次`);
      }

      if (typeof bugRate === 'number') {
        score -= Math.min(22, bugRate * 30);
        if (bugRate <= 0.2) reasons.push('历史 Bug 率较低');
        if (bugRate >= 0.5) warnings.push('历史 Bug 率偏高');
      }

      if (typeof stats?.delayRate === 'number' && stats.delayRate > 0.2) {
        score -= Math.min(12, stats.delayRate * 30);
        warnings.push('历史延期率偏高');
      }

      if (recentOvertimeHours >= 8) {
        score -= 8;
        warnings.push('近期加班较多');
      }

      const metrics: MemberRecommendationMetrics = {
        activeTaskCount,
        activeBugCount,
        deadlineConflictCount,
        remainingHours,
        recentOvertimeHours,
        projectParticipationCount,
        bugRate,
        availableInDays: remainingHours > 0 ? Math.ceil(remainingHours / 8) : 0,
      };

      return {
        name: candidate.name,
        role,
        score: clampScore(score),
        confidence,
        reasons,
        warnings,
        metrics,
      };
    })
    .filter((item): item is MemberRecommendationItem => Boolean(item));

  return ROLE_ORDER
    .map((role) => {
      const roleItems = items
        .filter((item) => item.role === role)
        .sort((a, b) => b.score - a.score || a.metrics.remainingHours - b.metrics.remainingHours || a.name.localeCompare(b.name));
      const demand = demandByRole[role];
      const missingCount = scoped ? demand.missingCount : roleItems.length;
      return {
        ...demand,
        requiredCount: scoped ? demand.requiredCount : demand.currentCount + roleItems.length,
        missingCount,
        items: roleItems.slice(0, missingCount),
      };
    })
    .filter((group) => group.items.length > 0);
}

export function addRecommendedVersionMembers(
  currentMembers: VersionMember[],
  selectedNames: Iterable<string>,
  recommendations: MemberRecommendationItem[],
): VersionMember[] {
  const existingNames = new Set(currentMembers.map((member) => member.name));
  const selectedNameSet = new Set(selectedNames);
  const additions = recommendations
    .filter((item) => selectedNameSet.has(item.name) && !existingNames.has(item.name))
    .map((item) => ({ name: item.name, role: item.role }));

  return [...currentMembers, ...additions];
}

export function getDefaultRecommendedMemberNames(groups: MemberRecommendationGroup[]): string[] {
  return groups.flatMap((group) => group.items.map((item) => item.name));
}
