import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { randomUUID } from 'crypto';
import { Between, Repository } from 'typeorm';
import { ExecutionRouterService } from '../executors/execution-router.service';
import { InspectionStepLog } from '../inspector/inspector.types';
import { InspectionTaskEntity } from '../tasks/inspection-task.entity';
import { endOfDay, parseReportDate, startOfDay } from '../utils/date';
import { InspectionRunEntity } from './inspection-run.entity';

@Injectable()
export class RunsService {
  constructor(
    @InjectRepository(InspectionRunEntity)
    private readonly runRepository: Repository<InspectionRunEntity>,
    @InjectRepository(InspectionTaskEntity)
    private readonly taskRepository: Repository<InspectionTaskEntity>,
    private readonly executionRouterService: ExecutionRouterService,
  ) {}

  async getRuns(filters: {
    taskId?: string;
    keyword?: string;
    status?: 'queued' | 'running' | 'success' | 'failed' | 'partial';
    executor?: 'browser' | 'agent-s';
    errorTag?: string;
    page?: number;
    pageSize?: number;
  }) {
    const page = Math.max(filters.page ?? 1, 1);
    const pageSize = Math.min(Math.max(filters.pageSize ?? 20, 1), 200);
    const query = this.buildRunQuery(filters);

    const [list, total] = await query
      .orderBy('run.startedAt', 'DESC')
      .skip((page - 1) * pageSize)
      .take(pageSize)
      .getManyAndCount();

    return { list, total, page, pageSize };
  }

  async getRunDetail(runId: string) {
    const run = await this.runRepository.findOne({
      where: { id: runId },
      relations: { task: true },
    });

    if (!run) {
      throw new NotFoundException('执行记录不存在');
    }

    return {
      run,
      evidence: {
        screenshots: this.parseJsonArray<string>(run.screenshotUrls),
        stepLogs: this.parseJsonArray<InspectionStepLog>(run.stepLogsJson),
        pageUrl: run.pageUrl,
        domSummary: run.domSummary,
        resultJsonUrl: this.buildResultJsonUrl(run),
      },
      context: null,
      diagnosis: {
        executor: run.executor,
        routePolicy: this.resolveRoutePolicy(run),
      },
    };
  }

  async getInspectionReport(reportDate?: string) {
    const baseDate = parseReportDate(reportDate);
    const dayStart = startOfDay(baseDate);
    const dayEnd = endOfDay(baseDate);
    const tasks = await this.taskRepository.find({
      order: { createdAt: 'DESC' },
    });

    const items = await Promise.all(
      tasks.map(async (task) => {
        const latestRun = await this.runRepository.findOne({
          where: {
            taskId: task.id,
            startedAt: Between(dayStart, dayEnd),
          },
          order: { startedAt: 'DESC' },
        });

        return {
          task: {
            id: task.id,
            name: task.name,
            siteUrl: task.siteUrl,
            cronExpr: task.cronExpr,
            serviceKey: task.serviceKey,
            preferredExecutor: task.preferredExecutor,
            allowAgentFallback: task.allowAgentFallback,
            status: task.status,
            lastRunAt: task.lastRunAt,
          },
          latestRun: latestRun
            ? {
                id: latestRun.id,
                traceId: latestRun.traceId,
                status: latestRun.status,
                errorTag: latestRun.errorTag,
                failureReason: latestRun.failureReason,
                startedAt: latestRun.startedAt,
                endedAt: latestRun.endedAt,
                pageUrl: latestRun.pageUrl,
                domSummary: latestRun.domSummary,
                executor: latestRun.executor,
                routePolicy: this.resolveRoutePolicy(latestRun),
                screenshots: this.parseJsonArray<string>(
                  latestRun.screenshotUrls,
                ),
                resultJsonUrl: this.buildResultJsonUrl(latestRun),
              }
            : null,
        };
      }),
    );

    return {
      generatedAt: new Date().toISOString(),
      reportDate: dayStart.toISOString(),
      totalTasks: tasks.length,
      coveredTasks: items.filter((item) => item.latestRun).length,
      successTasks: items.filter((item) =>
        ['success', 'partial'].includes(item.latestRun?.status ?? ''),
      ).length,
      exceptionTasks: items.filter((item) =>
        ['failed', 'partial'].includes(item.latestRun?.status ?? ''),
      ).length,
      items,
    };
  }
  async executeTask(taskId: string) {
    const task = await this.taskRepository.findOneBy({ id: taskId });

    if (!task) {
      throw new NotFoundException('巡检任务不存在');
    }
    if (task.status !== 'enabled') {
      throw new BadRequestException('任务已停用，不能执行。');
    }

    const traceId = randomUUID();
    const now = new Date();

    const runningRun = this.runRepository.create({
      taskId: task.id,
      traceId,
      status: 'running',
      errorTag: null,
      failureReason: null,
      screenshotUrls: null,
      stepLogsJson: null,
      pageUrl: null,
      domSummary: null,
      executor: task.preferredExecutor,
      routePolicy: null,
      startedAt: now,
      endedAt: null,
    });

    await this.runRepository.save(runningRun);

    try {
      const executionResult = await this.executionRouterService.executeTask(
        task,
        traceId,
      );

      runningRun.status = executionResult.status;
      runningRun.errorTag = executionResult.errorTag;
      runningRun.failureReason = executionResult.failureReason;
      runningRun.executor = executionResult.executor;
      runningRun.routePolicy = executionResult.routePolicy;
      runningRun.screenshotUrls = JSON.stringify(
        executionResult.screenshotPaths,
      );
      runningRun.stepLogsJson = JSON.stringify(executionResult.stepLogs);
      runningRun.pageUrl = executionResult.pageUrl;
      runningRun.domSummary = executionResult.domSummary;
      runningRun.endedAt = new Date();

      const completedRun = await this.runRepository.save(runningRun);

      task.lastRunAt = now;
      await this.taskRepository.save(task);

      return {
        runId: completedRun.id,
        traceId,
        status: completedRun.status,
        errorTag: completedRun.errorTag,
      };
    } catch (error) {
      runningRun.status = 'failed';
      runningRun.errorTag = 'EXECUTION_ERROR';
      runningRun.failureReason =
        error instanceof Error ? error.message : '执行器内部异常';
      runningRun.endedAt = new Date();
      await this.runRepository.save(runningRun);
      throw error;
    }
  }

  async deleteRun(runId: string) {
    const run = await this.runRepository.findOneBy({ id: runId });
    if (!run) {
      throw new NotFoundException('执行记录不存在');
    }
    await this.runRepository.delete({ id: runId });
    return { deleted: true, runId };
  }

  async deleteRuns(input: {
    runIds?: string[];
    filters?: {
      taskId?: string;
      keyword?: string;
      status?: 'queued' | 'running' | 'success' | 'failed' | 'partial';
      executor?: 'browser' | 'agent-s';
      errorTag?: string;
    };
  }) {
    const runIds = (input.runIds ?? []).filter(Boolean);
    if (!runIds.length && !input.filters) {
      throw new BadRequestException('请提供要删除的执行记录。');
    }

    const idsToDelete = runIds.length
      ? runIds
      : (
          await this.buildRunQuery(input.filters ?? {})
            .select('run.id', 'id')
            .getRawMany<{ id: string }>()
        ).map((item) => item.id);

    if (!idsToDelete.length) {
      return { deleted: 0, runIds: [] as string[] };
    }

    await this.runRepository
      .createQueryBuilder()
      .delete()
      .from(InspectionRunEntity)
      .where('id IN (:...ids)', { ids: idsToDelete })
      .execute();

    return { deleted: idsToDelete.length, runIds: idsToDelete };
  }

  private parseJsonArray<T>(raw: string | null): T[] {
    if (!raw) {
      return [];
    }

    try {
      const parsed: unknown = JSON.parse(raw);
      return Array.isArray(parsed) ? (parsed as T[]) : [];
    } catch {
      return [];
    }
  }

  private buildResultJsonUrl(
    run: Pick<InspectionRunEntity, 'executor' | 'traceId'>,
  ) {
    if (run.executor === 'agent-s') {
      return `/agent-runs/${run.traceId}/result.json`;
    }

    return `/runs/${run.traceId}/result.json`;
  }

  private resolveRoutePolicy(
    run: Pick<InspectionRunEntity, 'routePolicy' | 'executor'>,
  ) {
    if (run.routePolicy) {
      return run.routePolicy;
    }

    return run.executor === 'agent-s' ? 'agent-only' : 'browser-only';
  }

  private buildRunQuery(filters: {
    taskId?: string;
    keyword?: string;
    status?: 'queued' | 'running' | 'success' | 'failed' | 'partial';
    executor?: 'browser' | 'agent-s';
    errorTag?: string;
  }) {
    const query = this.runRepository
      .createQueryBuilder('run')
      .leftJoinAndSelect('run.task', 'task');

    if (filters.taskId) {
      query.andWhere('run.taskId = :taskId', { taskId: filters.taskId });
    }
    if (filters.status) {
      query.andWhere('run.status = :status', { status: filters.status });
    }
    if (filters.executor) {
      query.andWhere('run.executor = :executor', { executor: filters.executor });
    }
    if (filters.errorTag) {
      query.andWhere('run.errorTag LIKE :errorTag', {
        errorTag: `%${filters.errorTag}%`,
      });
    }
    if (filters.keyword) {
      query.andWhere(
        '(task.name LIKE :keyword OR run.traceId LIKE :keyword OR IFNULL(run.errorTag, \'\') LIKE :keyword OR IFNULL(run.failureReason, \'\') LIKE :keyword)',
        { keyword: `%${filters.keyword}%` },
      );
    }

    return query;
  }
}


