import { BadRequestException, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { assertAdmin } from '../admin/admin-access';
import { ApiCallLog } from '../third-party/entities/api-call-log.entity';
import { maskSensitivePayload } from '../third-party/third-party-security';
import { ThirdPartyClientService } from '../third-party/third-party-client.service';
import { User } from '../users/entities/user.entity';
import type {
  OrderLogHistoryQuery,
  QueryStreamOrderLogDto,
  QueryThirdPartyOrderLogDto,
  ThirdPartyOrderLogType,
} from './dto/order-logs.dto';

const logTypeActs: Record<ThirdPartyOrderLogType, string> = {
  normal: 'cha_logwk',
  zhs: 'cha_log',
  yjy: 'get_yjy_study_log',
};

@Injectable()
export class OrderLogsService {
  constructor(
    private readonly configService: ConfigService,
    private readonly thirdPartyClientService: ThirdPartyClientService,
    @InjectRepository(ApiCallLog)
    private readonly apiCallLogsRepository: Repository<ApiCallLog>,
  ) {}

  async queryThirdPartyLog(actor: User, dto: QueryThirdPartyOrderLogDto) {
    assertAdmin(actor);
    const orderId = this.getRequiredOrderId(dto.orderId);
    const type = this.getLogType(dto.type);
    const act = logTypeActs[type];
    const response = await this.thirdPartyClientService.call(act, {
      id: orderId,
    });

    return {
      provider: 'biedawo',
      type,
      act,
      orderId,
      queriedAt: new Date().toISOString(),
      response,
      entries: this.normalizeEntries(response),
    };
  }

  async queryStreamLog(actor: User, dto: QueryStreamOrderLogDto) {
    assertAdmin(actor);
    const orderId = this.getRequiredOrderId(dto.orderId);
    const startedAt = Date.now();
    const requestUrl = this.buildStreamLogUrl(orderId);
    let httpStatus: number | null = null;
    let responsePayload: unknown | null = null;
    let errorMessage: string | null = null;
    let logged = false;

    try {
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 15_000);
      try {
        const response = await fetch(requestUrl, {
          method: 'GET',
          signal: controller.signal,
        });
        httpStatus = response.status;
        const text = await response.text();
        responsePayload = this.parseStreamText(text);
        const status = response.ok ? 'success' : 'failure';
        if (!response.ok) {
          errorMessage = `HTTP ${response.status}`;
        }

        await this.writeStreamCallLog({
          orderId,
          requestUrl,
          status,
          httpStatus,
          durationMs: Date.now() - startedAt,
          responsePayload,
          errorMessage,
        });
        logged = true;

        if (!response.ok) {
          throw new BadRequestException(
            `第三方 Stream 日志请求失败：HTTP ${response.status}`,
          );
        }

        return {
          provider: 'biedawo',
          type: 'stream',
          act: 'streamLogs',
          orderId,
          queriedAt: new Date().toISOString(),
          response: responsePayload,
          entries: this.normalizeStreamEntries(responsePayload),
        };
      } finally {
        clearTimeout(timeout);
      }
    } catch (error) {
      const message = this.getErrorMessage(error);
      if (!errorMessage) {
        errorMessage = message;
      }
      if (!httpStatus) {
        responsePayload = null;
      }
      if (!logged) {
        await this.writeStreamCallLog({
          orderId,
          requestUrl,
          status: 'failure',
          httpStatus,
          durationMs: Date.now() - startedAt,
          responsePayload,
          errorMessage,
        });
      }
      if (error instanceof BadRequestException) {
        throw error;
      }
      throw new BadRequestException(message);
    }
  }

  async listHistory(actor: User, query: OrderLogHistoryQuery) {
    assertAdmin(actor);
    const page = Math.max(Number(query.page || 1), 1);
    const pageSize = Math.min(Math.max(Number(query.pageSize || 10), 1), 100);
    const type = String(query.type || '').trim();
    const status = String(query.status || '').trim();
    const orderId = String(query.orderId || '').trim();
    const acts = type
      ? [this.typeToAct(type)]
      : Object.values(logTypeActs).concat('streamLogs');

    const builder = this.apiCallLogsRepository
      .createQueryBuilder('log')
      .where('log.act IN (:...acts)', { acts })
      .orderBy('log.createdAt', 'DESC')
      .skip((page - 1) * pageSize)
      .take(pageSize);

    if (status) {
      builder.andWhere('log.status = :status', { status });
    }
    if (orderId) {
      builder.andWhere(
        'JSON_UNQUOTE(JSON_EXTRACT(log.requestPayload, "$.id")) LIKE :orderId',
        { orderId: `%${orderId}%` },
      );
    }

    const [list, total] = await builder.getManyAndCount();
    return { list, total, page, pageSize };
  }

  private getRequiredOrderId(value: unknown) {
    const orderId = String(value || '').trim();
    if (!orderId) {
      throw new BadRequestException('请填写第三方订单 ID');
    }
    return orderId;
  }

  private getLogType(value: unknown): ThirdPartyOrderLogType {
    const type = String(value || 'normal').trim() as ThirdPartyOrderLogType;
    if (!Object.hasOwn(logTypeActs, type)) {
      throw new BadRequestException('不支持的日志类型');
    }
    return type;
  }

  private typeToAct(type: string) {
    if (type === 'stream') {
      return 'streamLogs';
    }
    return logTypeActs[this.getLogType(type)];
  }

  private buildStreamLogUrl(orderId: string) {
    const apiUrl = new URL(
      this.configService.getOrThrow<string>('WK_BASE_URL'),
    );
    const streamUrl = new URL('/api/streamLogs', apiUrl.origin);
    streamUrl.searchParams.set('id', orderId);
    return streamUrl.toString();
  }

  private parseStreamText(text: string) {
    const lines = text
      .split(/\r?\n/)
      .map((line) => line.trim())
      .filter(Boolean);

    const events = lines.map((line) => {
      if (!line.startsWith('data:')) {
        return { raw: line };
      }
      const data = line.replace(/^data:\s*/, '');
      try {
        return JSON.parse(data) as unknown;
      } catch {
        return { raw: data };
      }
    });

    return {
      raw: text,
      events,
    };
  }

  private normalizeEntries(response: unknown) {
    const data = this.extractData(response);
    if (Array.isArray(data)) {
      return data;
    }
    if (data && typeof data === 'object') {
      const body = data as Record<string, unknown>;
      for (const key of ['list', 'logs', 'records', 'rows']) {
        if (Array.isArray(body[key])) {
          return body[key];
        }
      }
    }
    return [];
  }

  private normalizeStreamEntries(response: unknown) {
    if (!response || typeof response !== 'object') {
      return [];
    }
    const events = (response as { events?: unknown }).events;
    return Array.isArray(events) ? events : [];
  }

  private extractData(response: unknown) {
    if (!response || typeof response !== 'object') {
      return response;
    }
    const body = response as Record<string, unknown>;
    return body.data ?? body.result ?? body.rows ?? body;
  }

  private async writeStreamCallLog(input: {
    orderId: string;
    requestUrl: string;
    status: 'success' | 'failure';
    httpStatus: number | null;
    durationMs: number;
    responsePayload: unknown | null;
    errorMessage: string | null;
  }) {
    await this.thirdPartyClientService.writeLog({
      act: 'streamLogs',
      method: 'GET',
      url: input.requestUrl,
      status: input.status,
      httpStatus: input.httpStatus,
      durationMs: input.durationMs,
      requestPayload: maskSensitivePayload({
        act: 'streamLogs',
        id: input.orderId,
      }),
      responsePayload: maskSensitivePayload(input.responsePayload),
      errorMessage: input.errorMessage,
    });
  }

  private getErrorMessage(error: unknown) {
    if (error instanceof Error) {
      return error.name === 'AbortError'
        ? '第三方 Stream 日志请求超时'
        : error.message;
    }
    return '第三方 Stream 日志请求失败';
  }
}
