import {
  BadRequestException,
  ConflictException,
  NotFoundException,
} from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { AiReview } from './entities/ai-review.entity';
import type {
  AiReviewSection,
  AiReviewSectionType,
} from './entities/ai-review.entity';
import { parseSections } from './ai-review.workbook';
import { importOneSection } from './ai-review.import-relations';
import {
  emptySectionResult,
  MAX_ISSUES,
  SECTION_DEPENDENCIES,
  SECTION_KEY_RE,
  SECTION_ORDER,
  sectionResultMessage,
  sectionStatus,
} from './ai-review.shared';
import type {
  AiReviewSectionResult,
  AiReviewStepSubmitResult,
  AiReviewSubmitResult,
} from './ai-review.shared';

export interface AiReviewSubmitContext {
  reviews: Repository<AiReview>;
  dataSource: DataSource;
}

/**
 * Confirm one section in its own transaction.
 *
 * Row-level problems become section issues and are skipped instead of
 * failing the section. A dependency violation or an already-submitted
 * section throws ConflictException; unexpected import errors mark the
 * section as failed and are rethrown so the caller can retry later.
 */
export async function submitSection(
  context: AiReviewSubmitContext,
  reviewId: string,
  userId: number,
  sectionKey: string,
): Promise<AiReviewStepSubmitResult> {
  if (!SECTION_KEY_RE.test(sectionKey)) {
    throw new BadRequestException(`分表标识无效: ${sectionKey}`);
  }
  try {
    return await context.dataSource.transaction(async (manager) => {
      const review = await manager.findOne(AiReview, {
        where: { id: reviewId, userId },
      });
      if (!review) throw new NotFoundException('导入预览不存在');
      if (review.status === 'submitted') {
        throw new ConflictException('导入已全部确认，无需重复确认');
      }
      if (review.status === 'expired') {
        throw new ConflictException('导入预览已失效，请重新生成预览');
      }
      const sections = parseSections(review.sectionsJson);
      const index = sections.findIndex((section) => section.key === sectionKey);
      if (index === -1) throw new NotFoundException(`分表不存在: ${sectionKey}`);
      const section = sections[index];
      const sectionType = section.type;
      if (section.status === 'submitted') {
        throw new ConflictException(`分表「${section.title}」已确认导入`);
      }
      const dependency = unmetDependency(sections, sectionType);
      if (dependency) {
        throw new ConflictException(
          dependency.step === -1
            ? `「${dependency.title}」尚未导入，请先确认对应分表`
            : `请先确认第 ${dependency.step + 1} 步「${dependency.title}」`,
        );
      }
      const result = await importOneSection(section, manager);
      const message = sectionResultMessage(sectionType, result);
      section.status = 'submitted';
      section.resultSummary = JSON.stringify({ ...result, message });
      section.submittedAt = new Date().toISOString();
      section.issues = mergeIssues(section.issues, result.issues);
      review.sectionsJson = JSON.stringify(sections);
      if (sections.every((item) => sectionStatus(item) === 'submitted')) {
        review.status = 'submitted';
        review.resultSummary = JSON.stringify(buildAggregateResult(sections));
        review.submittedAt = new Date();
      }
      await manager.save(review);
      return { review, result, message };
    });
  } catch (error) {
    if (
      error instanceof ConflictException ||
      error instanceof NotFoundException ||
      error instanceof BadRequestException
    ) {
      throw error;
    }
    const message =
      error instanceof Error ? error.message.slice(0, 200) : '分表导入失败';
    await markSectionFailed(context, reviewId, userId, sectionKey, message);
    throw error;
  }
}

/**
 * Confirm every pending section in dependency order, each inside its
 * own transaction. Unexpected failures are persisted per section and
 * do not stop the remaining sections from being attempted.
 */
export async function submitAll(
  context: AiReviewSubmitContext,
  reviewId: string,
  userId: number,
): Promise<{
  review: AiReview;
  result: AiReviewSubmitResult;
}> {
  const initial = await findOwned(context.reviews, reviewId, userId);
  if (initial.status === 'expired') {
    throw new ConflictException('导入预览已失效，请重新生成预览');
  }
  const sections = parseSections(initial.sectionsJson);
  const result = buildAggregateResult(sections);

  for (const type of SECTION_ORDER) {
    for (const section of sections.filter((item) => item.type === type)) {
      if (section.status === 'submitted') continue;
      try {
        const step = await submitSection(context, reviewId, userId, section.key);
        mergeStepResult(result, type, step.result);
      } catch (error) {
        if (
          error instanceof ConflictException ||
          error instanceof NotFoundException ||
          error instanceof BadRequestException
        ) {
          const issue = error.message;
          const empty = emptySectionResult(type);
          mergeStepResult(result, type, {
            ...empty,
            issues: [...empty.issues, issue],
          });
          continue;
        }
        const empty = emptySectionResult(type);
        mergeStepResult(result, type, {
          ...empty,
          issues: [
            ...empty.issues,
            error instanceof Error ? error.message.slice(0, 200) : '分表导入失败',
          ],
        });
      }
    }
  }

  result.message = buildAggregateMessage(result);
  const review = await findOwned(context.reviews, reviewId, userId);
  return { review, result };
}

/**
 * Confirm every sheet of one business type, each in its own transaction.
 * A step that fails is marked `failed` and the remaining sheets still run;
 * the latest review is returned even when some sheets failed. Dependencies
 * are evaluated up front so an unmet prerequisite returns 409 before any
 * import is attempted.
 */
export async function submitGroup(
  context: AiReviewSubmitContext,
  reviewId: string,
  userId: number,
  type: AiReviewSectionType,
): Promise<{ review: AiReview }> {
  if (!['students', 'rooms', 'transfers', 'checkins'].includes(type)) {
    throw new BadRequestException(`业务类型不支持: ${String(type)}`);
  }
  const initial = await findOwned(context.reviews, reviewId, userId);
  if (initial.status === 'submitted') {
    throw new ConflictException('导入已全部确认，无需重复确认');
  }
  if (initial.status === 'expired') {
    throw new ConflictException('导入预览已失效，请重新生成预览');
  }
  const sections = parseSections(initial.sectionsJson);
  const group = sections.filter((section) => section.type === type);
  if (group.length === 0) throw new NotFoundException(`分表类型不存在: ${type}`);
  if (group.every((section) => section.status === 'submitted')) {
    return { review: initial };
  }

  const dependency = unmetDependency(sections, type);
  if (dependency) {
    throw new ConflictException(
      dependency.step === -1
        ? `「${dependency.title}」尚未导入，请先确认对应分表`
        : `请先确认第 ${dependency.step + 1} 步「${dependency.title}」`,
    );
  }

  for (const section of group) {
    if (section.status === 'submitted') continue;
    try {
      await submitSection(context, reviewId, userId, section.key);
    } catch {
      // submitSection already marks unexpected failures; expected conflicts
      // (e.g. a concurrent duplicate confirm) are also non-blocking here.
    }
  }
  return { review: await findOwned(context.reviews, reviewId, userId) };
}

export async function findOwned(
  reviews: Repository<AiReview>,
  reviewId: string,
  userId: number,
): Promise<AiReview> {
  const review = await reviews.findOne({
    where: { id: reviewId, userId },
  });
  if (!review) throw new NotFoundException('导入预览不存在');
  return review;
}

export function mergeStepResult(
  target: AiReviewSubmitResult,
  key: AiReviewSectionType,
  value: AiReviewSectionResult,
): void {
  if (key === 'students' || key === 'rooms') {
    const created = (value as { created: number }).created;
    target[key].created += created;
    target[key].skipped += value.skipped;
    target[key].issues = mergeIssues(target[key].issues, value.issues);
  } else {
    const completed = (value as { completed: number }).completed;
    target[key].completed += completed;
    target[key].skipped += value.skipped;
    target[key].issues = mergeIssues(target[key].issues, value.issues);
  }
}

export function buildAggregateResult(sections: AiReviewSection[]): AiReviewSubmitResult {
  const result: AiReviewSubmitResult = {
    students: { created: 0, skipped: 0, issues: [] },
    rooms: { created: 0, skipped: 0, issues: [] },
    transfers: { completed: 0, skipped: 0, issues: [] },
    checkins: { completed: 0, skipped: 0, issues: [] },
    message: '',
  };
  for (const section of sections) {
    const stored = parseStoredSectionResult(section);
    if (!stored) continue;
    mergeStepResult(result, section.type, stored);
  }
  result.message = buildAggregateMessage(result);
  return result;
}

export function buildAggregateMessage(result: AiReviewSubmitResult): string {
  const totalSkipped =
    result.students.skipped +
    result.rooms.skipped +
    result.transfers.skipped +
    result.checkins.skipped;
  return (
    `成功导入学生 ${result.students.created} 人、宿舍 ${result.rooms.created} 间、` +
    `换宿 ${result.transfers.completed} 条、入住 ${result.checkins.completed} 条；跳过 ${totalSkipped} 条`
  );
}

export function parseStoredSectionResult(
  section: AiReviewSection,
): AiReviewSectionResult | null {
  if (section.status !== 'submitted' || !section.resultSummary) return null;
  try {
    const parsed = JSON.parse(section.resultSummary) as Record<string, unknown>;
    const skipped = Number(parsed.skipped) || 0;
    const issues = Array.isArray(parsed.issues)
      ? parsed.issues.filter((item): item is string => typeof item === 'string')
      : [];
    if (section.type === 'transfers' || section.type === 'checkins') {
      return {
        completed: Number(parsed.completed) || 0,
        skipped,
        issues,
      };
    }
    return {
      created: Number(parsed.created) || 0,
      skipped,
      issues,
    };
  } catch {
    return null;
  }
}

export function mergeIssues(existing: string[], incoming: string[]): string[] {
  return [...new Set([...existing, ...incoming])].slice(-MAX_ISSUES);
}

export function unmetDependency(
  sections: AiReviewSection[],
  sectionType: AiReviewSectionType,
): { step: number; title: string } | null {
  const dependencies = SECTION_DEPENDENCIES[sectionType] ?? [];
  for (const dependencyType of dependencies) {
    const matches = sections.filter((section) => section.type === dependencyType);
    if (matches.length === 0) {
      return { step: -1, title: dependencyType };
    }
    for (const section of matches) {
      if (sectionStatus(section) !== 'submitted') {
        return { step: sections.indexOf(section), title: section.title };
      }
    }
  }
  return null;
}

export async function markSectionFailed(
  context: AiReviewSubmitContext,
  reviewId: string,
  userId: number,
  sectionKey: string,
  message: string,
): Promise<void> {
  try {
    await context.dataSource.transaction(async (manager) => {
      const review = await manager.findOne(AiReview, {
        where: { id: reviewId, userId },
      });
      if (!review || review.status === 'submitted' || review.status === 'expired') return;
      const sections = parseSections(review.sectionsJson);
      const section = sections.find((item) => item.key === sectionKey);
      if (!section || section.status === 'submitted') return;
      section.status = 'failed';
      section.resultSummary = message;
      section.issues = mergeIssues(section.issues, [`导入失败：${message}`]);
      review.sectionsJson = JSON.stringify(sections);
      await manager.save(review);
    });
  } catch {
    // Failure recording is best-effort; the original error is more useful.
  }
}
