import { STAGES, type Stage } from './stage';
import type { VersionPlan } from './version-plan';
import type { DevTask } from './dev-task';
import type { TestCase } from './test-case';
import type { Bug, BugSeverity } from './bug';
import type { OvertimeRecord } from './overtime';
import type { VersionStatus } from './version-status';
import { getActualHours as getDevTaskActualHours } from './dev-task';
import { getTestCaseActualHours } from './test-case';
import { getBugActualHours } from './bug';
import { calcActualElapsedHours } from './work-hours';
import { formatDateTime } from './format';

export interface StageEffortMetric {
  actualHours: number;
  estimateHours?: number;
  aiEstimateHours?: number;
  showEstimates?: boolean;
}

export interface StageProgressState {
  percent: number;
  status: 'idle' | 'active' | 'done';
}

export type StageProgressWithEffort = StageProgressState & StageEffortMetric;

export interface PersonalEffortItem {
  name: string;
  actualHours: number;
  overtimeHours: number;
  total: number;
}

export interface BugSeverityRankingItem {
  assigneeId: string;
  critical: number;
  major: number;
  minor: number;
  trivial: number;
  total: number;
}

export interface VersionOverviewEffortTotals {
  actualHours: number;
  overtimeHours: number;
}

export interface VersionTimelineSummary {
  actualStartIso: string | null;
  expectedReleaseIso: string | null;
  actualReleaseIso: string | null;
  actualEndIso: string | null;
  isTerminalVersion: boolean;
  actualHours: number;
  overdueDays: number;
}

function roundHalf(hours: number): number {
  return Math.round(hours * 2) / 2;
}

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

function roundHundredth(hours: number): number {
  return Math.round(hours * 100) / 100;
}

function sumAiEstimate<T>(items: T[], getValue: (item: T) => number | undefined): number | undefined {
  const total = items.reduce((sum, item) => {
    const value = getValue(item);
    return sum + (typeof value === 'number' && value > 0 ? value : 0);
  }, 0);
  return total > 0 ? roundHundredth(total) : undefined;
}

function sumEstimate<T>(items: T[], getValue: (item: T) => number | undefined): number | undefined {
  const total = items.reduce((sum, item) => {
    const value = getValue(item);
    return sum + (typeof value === 'number' && value > 0 ? value : 0);
  }, 0);
  return total > 0 ? roundHundredth(total) : undefined;
}

function getPlanActualHours(plan: VersionPlan, now: Date): number {
  if (!plan.actualStartAt) return 0;
  return calcActualElapsedHours(plan.actualStartAt, plan.completedAt ?? now.toISOString());
}

function sumActualHours<T>(items: T[], getValue: (item: T) => number): number {
  return roundHalf(items.reduce((sum, item) => sum + getValue(item), 0));
}

export function calcStageEffortMetrics(input: {
  plans: VersionPlan[];
  devTasks: DevTask[];
  testCases: TestCase[];
  bugs: Bug[];
  now?: Date;
}): Record<Stage, StageEffortMetric> {
  const now = input.now ?? new Date();
  const researchPlans = input.plans.filter((p) => p.type === 'research');
  const productPlans = input.plans.filter((p) => p.type === 'product');
  const uiPlans = input.plans.filter((p) => p.type === 'ui');

  return {
    requirement: {
      actualHours: sumActualHours(researchPlans, (plan) => getPlanActualHours(plan, now)),
    },
    product_design: {
      actualHours: sumActualHours(productPlans, (plan) => getPlanActualHours(plan, now)),
    },
    ui_design: {
      actualHours: sumActualHours(uiPlans, (plan) => getPlanActualHours(plan, now)),
    },
    dev: {
      actualHours: sumActualHours(input.devTasks, (task) => getDevTaskActualHours(task, now)),
      estimateHours: sumEstimate(input.devTasks, (task) => task.estimateHours),
      aiEstimateHours: sumAiEstimate(input.devTasks, (task) => task.aiEstimateHours),
      showEstimates: true,
    },
    testing: {
      actualHours: sumActualHours(input.testCases, (testCase) => getTestCaseActualHours(testCase, now)),
      estimateHours: sumEstimate(input.testCases, (testCase) => testCase.estimateHours),
      aiEstimateHours: sumAiEstimate(input.testCases, (testCase) => testCase.aiEstimateHours),
      showEstimates: true,
    },
    bug: {
      actualHours: sumActualHours(input.bugs, (bug) => getBugActualHours(bug, now)),
      estimateHours: sumEstimate(input.bugs, (bug) => bug.estimateHours),
      aiEstimateHours: sumAiEstimate(input.bugs, (bug) => bug.aiEstimateHours),
      showEstimates: true,
    },
  };
}

export function mergeStageProgressWithEffort(
  progress: Partial<Record<Stage, StageProgressState>>,
  effortMetrics: Record<Stage, StageEffortMetric>,
): Record<Stage, StageProgressWithEffort> {
  return STAGES.reduce((acc, stage) => {
    const state = progress[stage.key];
    acc[stage.key] = {
      percent: state?.percent ?? 0,
      status: state?.status ?? 'idle',
      ...effortMetrics[stage.key],
    };
    return acc;
  }, {} as Record<Stage, StageProgressWithEffort>);
}

export function getVersionCardDefaultExpanded(status: VersionStatus): boolean {
  return status === 'developing';
}

export function formatVersionOverviewDateTime(value?: string | null): string {
  if (!value) return '-';
  return value.includes('T') ? formatDateTime(value) : value;
}

export function buildVersionTimelineSummary(input: {
  status: VersionStatus;
  startDate?: string | null;
  expectedReleaseDate?: string | null;
  releaseDate?: string | null;
  plans: VersionPlan[];
  devTasks: DevTask[];
  testCases: TestCase[];
  bugs: Bug[];
  now?: Date;
}): VersionTimelineSummary {
  const now = input.now ?? new Date();
  const startDates: string[] = [];

  input.plans.forEach((plan) => {
    if (plan.actualStartAt) {
      startDates.push(plan.actualStartAt);
    } else if (plan.status === 'pending' && plan.startTime && new Date(plan.startTime) <= now) {
      startDates.push(plan.startTime);
    }
  });
  input.devTasks.forEach((task) => {
    if (task.actualStartAt) startDates.push(task.actualStartAt);
  });
  input.testCases.forEach((testCase) => {
    if (testCase.startedAt) startDates.push(testCase.startedAt);
  });

  const actualStartIso = startDates.length > 0 ? startDates.sort()[0] : (input.startDate ?? null);

  const endDates: string[] = [];
  input.plans.forEach((plan) => {
    if (plan.completedAt) endDates.push(plan.completedAt);
  });
  input.devTasks.forEach((task) => {
    if (task.actualEndAt) endDates.push(task.actualEndAt);
  });
  input.testCases.forEach((testCase) => {
    if (testCase.completedAt) endDates.push(testCase.completedAt);
  });
  input.bugs.forEach((bug) => {
    if (bug.closedAt) endDates.push(bug.closedAt);
    else if (bug.resolvedAt) endDates.push(bug.resolvedAt);
    else if ((bug.status === 'closed' || bug.status === 'rejected') && bug.updatedAt) endDates.push(bug.updatedAt);
  });

  const actualEndIso = endDates.length > 0 ? endDates.sort().reverse()[0] : null;
  const isTerminalVersion = input.status === 'released' || input.status === 'closed';
  const actualHours = calcActualElapsedHours(actualStartIso, isTerminalVersion ? actualEndIso : now.toISOString());

  let overdueDays = 0;
  if (input.expectedReleaseDate && input.releaseDate) {
    const endDate = new Date(input.releaseDate);
    const deadlineDate = new Date(input.expectedReleaseDate);
    endDate.setHours(0, 0, 0, 0);
    deadlineDate.setHours(0, 0, 0, 0);
    overdueDays = Math.floor((endDate.getTime() - deadlineDate.getTime()) / (1000 * 60 * 60 * 24));
  }

  return {
    actualStartIso,
    expectedReleaseIso: input.expectedReleaseDate ?? null,
    actualReleaseIso: input.releaseDate ?? null,
    actualEndIso,
    isTerminalVersion,
    actualHours,
    overdueDays,
  };
}

export function calcVersionOverviewEffortTotals(input: {
  plans: VersionPlan[];
  devTasks: DevTask[];
  testCases: TestCase[];
  bugs: Bug[];
  overtimeRecords: OvertimeRecord[];
  now?: Date;
}): VersionOverviewEffortTotals {
  const now = input.now ?? new Date();
  const planHours = sumActualHours(input.plans, (plan) => getPlanActualHours(plan, now));
  const devHours = sumActualHours(input.devTasks, (task) => getDevTaskActualHours(task, now));
  const testHours = sumActualHours(input.testCases, (testCase) => getTestCaseActualHours(testCase, now));
  const bugHours = sumActualHours(input.bugs, (bug) => getBugActualHours(bug, now));
  const actualHours = roundHalf(planHours + devHours + testHours + bugHours);
  const overtimeHours = roundTenth(input.overtimeRecords.reduce((sum, record) => sum + record.duration, 0));

  return { actualHours, overtimeHours };
}

export function calcPersonalEffortRanking(input: {
  plans: VersionPlan[];
  devTasks: DevTask[];
  testCases: TestCase[];
  bugs: Bug[];
  overtimeRecords?: OvertimeRecord[];
  now?: Date;
}): PersonalEffortItem[] {
  const now = input.now ?? new Date();
  const personalHours = new Map<string, { actualHours: number; overtimeHours: number }>();

  const addActualHours = (name: string | undefined, hours: number) => {
    if (!name || hours <= 0) return;
    const prev = personalHours.get(name) || { actualHours: 0, overtimeHours: 0 };
    prev.actualHours += hours;
    personalHours.set(name, prev);
  };

  const addOvertimeHours = (name: string | undefined, hours: number) => {
    if (!name || hours <= 0) return;
    const prev = personalHours.get(name) || { actualHours: 0, overtimeHours: 0 };
    prev.overtimeHours += hours;
    personalHours.set(name, prev);
  };

  input.plans.forEach((plan) => {
    addActualHours(plan.owner, getPlanActualHours(plan, now));
  });

  input.devTasks.forEach((task) => {
    addActualHours(task.assigneeId, getDevTaskActualHours(task, now));
  });

  input.testCases.forEach((testCase) => {
    addActualHours(testCase.assigneeId, getTestCaseActualHours(testCase, now));
  });

  input.bugs.forEach((bug) => {
    addActualHours(bug.assigneeId, getBugActualHours(bug, now));
  });

  (input.overtimeRecords ?? []).forEach((record) => {
    addOvertimeHours(record.person, record.duration);
  });

  return Array.from(personalHours.entries())
    .map(([name, hours]) => {
      const actualHours = roundHalf(hours.actualHours);
      const overtimeHours = roundTenth(hours.overtimeHours);
      return {
        name,
        actualHours,
        overtimeHours,
        total: roundTenth(actualHours + overtimeHours),
      };
    })
    .sort((a, b) => b.total - a.total);
}

function createBugSeverityRankingItem(assigneeId: string): BugSeverityRankingItem {
  return {
    assigneeId,
    critical: 0,
    major: 0,
    minor: 0,
    trivial: 0,
    total: 0,
  };
}

export function calcBugSeverityRanking(bugs: Bug[]): BugSeverityRankingItem[] {
  const rows = new Map<string, BugSeverityRankingItem>();
  const severityOrder: BugSeverity[] = ['critical', 'major', 'minor', 'trivial'];

  bugs.forEach((bug) => {
    if (!bug.assigneeId || !severityOrder.includes(bug.severity)) return;
    const row = rows.get(bug.assigneeId) ?? createBugSeverityRankingItem(bug.assigneeId);
    row[bug.severity] += 1;
    row.total += 1;
    rows.set(bug.assigneeId, row);
  });

  return Array.from(rows.values()).sort(
    (a, b) =>
      b.total - a.total ||
      b.critical - a.critical ||
      b.major - a.major ||
      b.minor - a.minor ||
      b.trivial - a.trivial ||
      a.assigneeId.localeCompare(b.assigneeId),
  );
}
