import type { Priority } from './derive';
import type { DevTaskStatus, Reference } from './dev-task';
import type { Bug } from './bug';
import { DEFAULT_TEST_CATEGORY_ID } from './task-category';
import { calcActualElapsedHours, type TimeInterval } from './work-hours';
import { aggregateWorkEffort } from './work-effort-engine';

export type TestCaseStatus = 'pending' | 'running' | 'passed' | 'failed' | 'blocked';

export interface TestCase {
  id: string;
  caseNo: string;
  versionId: string;
  requirementId?: string;
  requirementName?: string;
  roundNo?: number;
  sourceCaseId?: string;
  title: string;
  description?: string;
  categoryId: string;
  priority: Priority;
  assigneeId?: string;
  status: TestCaseStatus;
  estimateHours?: number;
  aiEstimateHours?: number;
  plannedTestAt?: string;
  plannedEndAt?: string;
  startedAt?: string;
  completedAt?: string;
  executedAt?: string;
  executedBy?: string;
  failReason?: string;
  blockReason?: string;
  references?: Reference[];
  aiDraft?: boolean;
  aiDraftAt?: string;
  createdBy: string;
  createdAt: string;
  updatedAt: string;
}

export const TEST_CASE_STATUS_LABEL: Record<TestCaseStatus, string> = {
  pending: '待测试',
  running: '测试中',
  passed: '通过',
  failed: '不通过',
  blocked: '阻塞',
};

export const TEST_CASE_STATUS_COLOR: Record<TestCaseStatus, string> = {
  pending: 'bg-zinc-100 text-zinc-600',
  running: 'bg-blue-50 text-blue-600',
  passed: 'bg-emerald-50 text-emerald-600',
  failed: 'bg-red-50 text-red-600',
  blocked: 'bg-orange-50 text-orange-600',
};

export const TC_ALLOWED_TRANSITIONS: Record<TestCaseStatus, TestCaseStatus[]> = {
  pending: ['running'],
  running: ['passed', 'failed', 'blocked'],
  passed: ['running'],
  failed: ['running'],
  blocked: ['running'],
};

export const TEST_CASE_STATUS_PROGRESS: Record<TestCaseStatus, number> = {
  pending: 0,
  running: 50,
  passed: 100,
  failed: 100,
  blocked: 100,
};

export function canTcTransition(from: TestCaseStatus, to: TestCaseStatus): boolean {
  return TC_ALLOWED_TRANSITIONS[from].includes(to);
}

export function generateCaseNo(existingCases: TestCase[]): string {
  const maxNum = existingCases.reduce((max, c) => {
    const num = parseInt(c.caseNo.replace('TC-', ''), 10);
    return isNaN(num) ? max : Math.max(max, num);
  }, 0);
  return `TC-${String(maxNum + 1).padStart(3, '0')}`;
}

export function normalizeTestCase(testCase: Partial<TestCase>, index = 0): TestCase {
  return {
    id: testCase.id || `tc-${index + 1}`,
    caseNo: testCase.caseNo || `TC-${String(index + 1).padStart(3, '0')}`,
    versionId: testCase.versionId || '',
    requirementId: testCase.requirementId,
    requirementName: testCase.requirementName,
    roundNo: getTestCaseRoundNo(testCase),
    sourceCaseId: testCase.sourceCaseId,
    title: testCase.title || `测试用例 ${index + 1}`,
    description: testCase.description,
    categoryId: testCase.categoryId || DEFAULT_TEST_CATEGORY_ID,
    priority: testCase.priority || 'P2',
    assigneeId: testCase.assigneeId,
    status: testCase.status || 'pending',
    estimateHours: typeof testCase.estimateHours === 'number' && testCase.estimateHours > 0 ? testCase.estimateHours : undefined,
    aiEstimateHours: typeof testCase.aiEstimateHours === 'number' && testCase.aiEstimateHours > 0 ? testCase.aiEstimateHours : undefined,
    plannedTestAt: testCase.plannedTestAt,
    plannedEndAt: testCase.plannedEndAt,
    startedAt: testCase.startedAt,
    completedAt: testCase.completedAt,
    executedAt: testCase.executedAt,
    executedBy: testCase.executedBy,
    failReason: testCase.failReason,
    blockReason: testCase.blockReason,
    references: testCase.references,
    aiDraft: testCase.aiDraft,
    aiDraftAt: testCase.aiDraftAt,
    createdBy: testCase.createdBy || '系统',
    createdAt: testCase.createdAt || new Date().toISOString(),
    updatedAt: testCase.updatedAt || new Date().toISOString(),
  };
}

export function normalizeTestCases(cases: Partial<TestCase>[] = []): TestCase[] {
  return cases.map((tc, index) => normalizeTestCase(tc, index));
}

export type CreateTestCaseInput = Omit<TestCase, 'id' | 'caseNo' | 'createdAt' | 'updatedAt' | 'status'>;

export function getTestCaseRoundNo(testCase: Partial<Pick<TestCase, 'roundNo'>>): number {
  const roundNo = testCase.roundNo;
  if (typeof roundNo !== 'number' || !Number.isFinite(roundNo) || roundNo < 1) return 1;
  return Math.floor(roundNo);
}

export function getNextTestRoundNo(cases: Pick<TestCase, 'roundNo'>[]): number {
  const maxRound = cases.reduce((max, testCase) => Math.max(max, getTestCaseRoundNo(testCase)), 0);
  return maxRound + 1;
}

export function isTestCaseTested(testCase: Pick<TestCase, 'status'>): boolean {
  return testCase.status === 'passed' || testCase.status === 'failed' || testCase.status === 'blocked';
}

export function canStartNextTestRound(cases: Pick<TestCase, 'roundNo' | 'status'>[]): boolean {
  const firstRoundCases = cases.filter((testCase) => getTestCaseRoundNo(testCase) === 1);
  if (firstRoundCases.length === 0) return false;
  const latestRoundNo = Math.max(...cases.map((testCase) => getTestCaseRoundNo(testCase)));
  const latestRoundCases = cases.filter((testCase) => getTestCaseRoundNo(testCase) === latestRoundNo);
  return latestRoundCases.length > 0 && latestRoundCases.every(isTestCaseTested);
}

export type RequirementDeliveryStatus = 'submitted' | 'pending';

type RequirementDevTaskRef = {
  requirementId: string;
  status: DevTaskStatus;
};

export function getRequirementDeliveryStatus(
  requirementId: string | undefined,
  devTasks: RequirementDevTaskRef[],
): RequirementDeliveryStatus {
  if (!requirementId) return 'pending';
  const requirementTasks = devTasks.filter((task) => task.requirementId === requirementId);
  if (requirementTasks.length === 0) return 'pending';
  return requirementTasks.every((task) => task.status === 'submitted') ? 'submitted' : 'pending';
}

export function copyTestCaseToRound(source: TestCase, roundNo: number, createdBy: string): CreateTestCaseInput {
  return {
    versionId: source.versionId,
    requirementId: source.requirementId,
    requirementName: source.requirementName,
    roundNo,
    sourceCaseId: source.sourceCaseId ?? source.id,
    title: source.title,
    description: source.description,
    categoryId: source.categoryId,
    priority: source.priority,
    estimateHours: source.estimateHours,
    aiEstimateHours: source.aiEstimateHours,
    plannedTestAt: source.plannedTestAt,
    plannedEndAt: source.plannedEndAt,
    assigneeId: source.assigneeId,
    startedAt: undefined,
    completedAt: undefined,
    executedAt: undefined,
    executedBy: undefined,
    failReason: undefined,
    blockReason: undefined,
    references: source.references,
    aiDraft: source.aiDraft,
    aiDraftAt: source.aiDraftAt,
    createdBy,
  };
}

type TestCaseBugRef = Pick<Bug, 'testCaseId' | 'status'>;

export function calcTestProgress(
  cases: TestCase[],
  bugs: TestCaseBugRef[] = [],
): { total: number; executed: number; passed: number; failed: number; blocked: number; passRate: number; completionRate: number } {
  const total = cases.length;
  if (total === 0) return { total: 0, executed: 0, passed: 0, failed: 0, blocked: 0, passRate: 0, completionRate: 0 };
  const passed = cases.filter((c) => c.status === 'passed').length;
  const failed = cases.filter((c) => c.status === 'failed').length;
  const blocked = cases.filter((c) => c.status === 'blocked').length;
  const effectiveBugCaseIds = new Set(
    bugs.filter((bug) => bug.status !== 'rejected').map((bug) => bug.testCaseId),
  );
  const executedCases = cases.filter(
    (c) => c.status === 'passed' || c.status === 'failed' || c.status === 'blocked' || effectiveBugCaseIds.has(c.id),
  );
  const cleanPassed = executedCases.filter((c) => c.status === 'passed' && !effectiveBugCaseIds.has(c.id)).length;
  const executed = executedCases.length;
  const passRate = executed > 0 ? Math.round((cleanPassed / executed) * 100) : 0;
  const completionRate = aggregateWorkEffort(cases.map((c) => ({
    estimateHours: getTestCaseEstimateHours(c),
    actualHours: getTestCaseActualHours(c),
    progress: TEST_CASE_STATUS_PROGRESS[c.status],
  }))).progress;
  return { total, executed, passed, failed, blocked, passRate, completionRate };
}

export function getTestCaseEstimateHours(tc: TestCase): number {
  if (typeof tc.estimateHours === 'number' && tc.estimateHours > 0) {
    return Number(tc.estimateHours.toFixed(2));
  }
  if (typeof tc.aiEstimateHours === 'number' && tc.aiEstimateHours > 0) {
    return Number(tc.aiEstimateHours.toFixed(2));
  }
  return 0;
}

export function needsTestCaseClaim(testCase: Pick<TestCase, 'assigneeId'>): boolean {
  return !testCase.assigneeId?.trim();
}

export function hasTestCasePlan(testCase: Pick<TestCase, 'plannedTestAt' | 'plannedEndAt'>): boolean {
  if (!testCase.plannedTestAt || !testCase.plannedEndAt) return false;
  const start = new Date(testCase.plannedTestAt).getTime();
  const end = new Date(testCase.plannedEndAt).getTime();
  return Number.isFinite(start) && Number.isFinite(end) && end > start;
}

export function canStartTestCase(
  testCase: Pick<TestCase, 'assigneeId' | 'plannedTestAt' | 'plannedEndAt'>,
): boolean {
  return !needsTestCaseClaim(testCase) && hasTestCasePlan(testCase);
}

export function getTestCaseActualHours(tc: TestCase, now: Date = new Date()): number {
  if (!tc.startedAt) return 0;
  const isTerminal = tc.status === 'passed' || tc.status === 'failed' || tc.status === 'blocked';
  const end = tc.completedAt ?? (isTerminal ? tc.updatedAt : now.toISOString());
  return calcActualElapsedHours(tc.startedAt, end);
}

export function aggregateTestCaseHours(cases: TestCase[], now: Date = new Date()): { estimate: number; actual: number } {
  const summary = aggregateWorkEffort(cases.map((c) => ({
    estimateHours: getTestCaseEstimateHours(c),
    actualHours: getTestCaseActualHours(c, now),
    progress: TEST_CASE_STATUS_PROGRESS[c.status],
  })));
  return { estimate: summary.estimateHours, actual: summary.actualHours };
}

export function aggregateTestCaseActualHours(cases: TestCase[], now: Date = new Date()): number {
  let sum = 0;
  for (const c of cases) sum += getTestCaseActualHours(c, now);
  return Math.round(sum * 2) / 2;
}

/**
 * 抽取每条已开始用例的 [startedAt, completedAt ?? now] 时间区间
 * 用于双口径耗时统计（calcTwoMetrics）
 */
export function testCaseIntervals(cases: TestCase[], now: Date = new Date()): TimeInterval[] {
  const out: TimeInterval[] = [];
  const nowIso = now.toISOString();
  for (const c of cases) {
    if (!c.startedAt) continue;
    const isTerminal = c.status === 'passed' || c.status === 'failed' || c.status === 'blocked';
    out.push({ start: c.startedAt, end: c.completedAt ?? (isTerminal ? c.updatedAt : nowIso) });
  }
  return out;
}
