import { Repository } from 'typeorm';
import { ToolCall, ToolCallResult, ResultType } from '../entities';
import { StartCallData, CallHistoryFilters, PaginatedResult } from '../dto';

export class ToolLoggerService {
  constructor(
    private readonly toolCallRepo: Repository<ToolCall>,
    private readonly resultRepo: Repository<ToolCallResult>
  ) {}

  async startCall(data: StartCallData): Promise<string> {
    const call = this.toolCallRepo.create({
      tenantId: data.tenantId,
      toolName: data.toolName,
      parameters: data.parameters,
      agentId: data.agentId,
      conversationId: data.conversationId,
      callerType: data.callerType,
      calledByUserId: data.userId,
      status: 'running',
      startedAt: new Date(),
    });

    const saved = await this.toolCallRepo.save(call);
    return saved.id;
  }

  async completeCall(callId: string, result: any): Promise<void> {
    const call = await this.toolCallRepo.findOne({ where: { id: callId } });
    if (!call) return;

    const duration = Date.now() - call.startedAt.getTime();

    await this.toolCallRepo.update(callId, {
      status: 'success',
      completedAt: new Date(),
      durationMs: duration,
    });

    await this.resultRepo.save({
      toolCallId: callId,
      result,
      resultType: this.getResultType(result),
    });
  }

  async failCall(callId: string, errorMessage: string, errorCode: string): Promise<void> {
    const call = await this.toolCallRepo.findOne({ where: { id: callId } });
    if (!call) return;

    const duration = Date.now() - call.startedAt.getTime();

    await this.toolCallRepo.update(callId, {
      status: 'error',
      completedAt: new Date(),
      durationMs: duration,
    });

    await this.resultRepo.save({
      toolCallId: callId,
      resultType: 'error',
      errorMessage,
      errorCode,
    });
  }

  async timeoutCall(callId: string): Promise<void> {
    const call = await this.toolCallRepo.findOne({ where: { id: callId } });
    if (!call) return;

    const duration = Date.now() - call.startedAt.getTime();

    await this.toolCallRepo.update(callId, {
      status: 'timeout',
      completedAt: new Date(),
      durationMs: duration,
    });

    await this.resultRepo.save({
      toolCallId: callId,
      resultType: 'error',
      errorMessage: 'Tool execution timed out',
      errorCode: 'TIMEOUT',
    });
  }

  async getCallHistory(
    tenantId: string,
    filters: CallHistoryFilters
  ): Promise<PaginatedResult<ToolCall>> {
    const qb = this.toolCallRepo
      .createQueryBuilder('tc')
      .leftJoinAndSelect('tc.result', 'result')
      .where('tc.tenant_id = :tenantId', { tenantId });

    if (filters.toolName) {
      qb.andWhere('tc.tool_name = :toolName', { toolName: filters.toolName });
    }

    if (filters.status) {
      qb.andWhere('tc.status = :status', { status: filters.status });
    }

    if (filters.startDate) {
      qb.andWhere('tc.created_at >= :startDate', { startDate: filters.startDate });
    }

    if (filters.endDate) {
      qb.andWhere('tc.created_at <= :endDate', { endDate: filters.endDate });
    }

    qb.orderBy('tc.created_at', 'DESC');
    qb.skip((filters.page - 1) * filters.limit);
    qb.take(filters.limit);

    const [data, total] = await qb.getManyAndCount();

    return { data, total, page: filters.page, limit: filters.limit };
  }

  async getCallById(id: string, tenantId: string): Promise<ToolCall | null> {
    return this.toolCallRepo.findOne({
      where: { id, tenantId },
      relations: ['result'],
    });
  }

  async getToolStats(
    tenantId: string,
    startDate: Date,
    endDate: Date
  ): Promise<{
    toolName: string;
    totalCalls: number;
    successfulCalls: number;
    failedCalls: number;
    avgDurationMs: number;
  }[]> {
    const result = await this.toolCallRepo
      .createQueryBuilder('tc')
      .select('tc.tool_name', 'toolName')
      .addSelect('COUNT(*)', 'totalCalls')
      .addSelect("COUNT(*) FILTER (WHERE tc.status = 'success')", 'successfulCalls')
      .addSelect("COUNT(*) FILTER (WHERE tc.status = 'error')", 'failedCalls')
      .addSelect('AVG(tc.duration_ms)', 'avgDurationMs')
      .where('tc.tenant_id = :tenantId', { tenantId })
      .andWhere('tc.created_at BETWEEN :startDate AND :endDate', { startDate, endDate })
      .groupBy('tc.tool_name')
      .orderBy('totalCalls', 'DESC')
      .getRawMany();

    return result.map((r) => ({
      toolName: r.toolName,
      totalCalls: parseInt(r.totalCalls) || 0,
      successfulCalls: parseInt(r.successfulCalls) || 0,
      failedCalls: parseInt(r.failedCalls) || 0,
      avgDurationMs: parseFloat(r.avgDurationMs) || 0,
    }));
  }

  private getResultType(result: any): ResultType {
    if (result === null) return 'null';
    if (Array.isArray(result)) return 'array';
    const type = typeof result;
    if (type === 'object') return 'object';
    if (type === 'string') return 'string';
    if (type === 'number') return 'number';
    if (type === 'boolean') return 'boolean';
    return 'object';
  }
}
