import {
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateCallDto } from './dto/create-call.dto';
import { FindCallsDto } from './dto/find-calls.dto';
import { UpdateCallStatusDto } from './dto/update-call-status.dto';
import { CreateSipConfigDto, UpdateSipConfigDto } from './dto/sip-config.dto';
import {
  CreateSipAccountDto,
  UpdateSipAccountDto,
  UpdateOwnSipAccountDto,
} from './dto/sip-account.dto';
import { Prisma, CallStatus } from '@prisma/client';

export interface PaginatedResult<T> {
  data: T[];
  nextCursor: string | null;
  hasMore: boolean;
}

export interface CallStats {
  totalCalls: number;
  answeredCalls: number;
  missedCalls: number;
  averageDuration: number;
  answeredRate: number;
}

const CALL_INCLUDE = {
  caller: { select: { id: true, firstName: true, lastName: true } },
  client: {
    select: { id: true, firstName: true, lastName: true, phone: true },
  },
};

@Injectable()
export class SipService {
  constructor(private readonly prisma: PrismaService) {}

  // ─── Call Log ──────────────────────────────────────────────────────

  /** Find calls with cursor-based pagination and filters. */
  async findCalls(
    clubId: string,
    params: FindCallsDto,
  ): Promise<PaginatedResult<any>> {
    const { cursor, limit = 20, trainerId, clientId, status, dateFrom, dateTo } = params;

    const where: Prisma.SipCallWhereInput = {
      clubId,
      ...(trainerId ? { callerId: trainerId } : {}),
      ...(clientId ? { clientId } : {}),
      ...(status ? { status } : {}),
      ...this.buildDateFilter(dateFrom, dateTo),
    };

    const calls = await this.prisma.sipCall.findMany({
      where,
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { startedAt: 'desc' },
      include: CALL_INCLUDE,
    });

    const hasMore = calls.length > limit;
    const data = hasMore ? calls.slice(0, -1) : calls;

    return {
      data,
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    };
  }

  /** Find a single call by ID with all relations. */
  async findCallById(clubId: string, id: string) {
    const call = await this.prisma.sipCall.findFirst({
      where: { id, clubId },
      include: CALL_INCLUDE,
    });

    if (!call) {
      throw new NotFoundException(`Call with id ${id} not found`);
    }
    return call;
  }

  /** Create a new call record with INITIATED status. */
  async createCall(clubId: string, callerId: string, dto: CreateCallDto) {
    if (dto.clientId) {
      await this.ensureClientExists(clubId, dto.clientId);
    }

    const config = await this.prisma.sipConfig.findUnique({
      where: { clubId },
      select: { callerId: true },
    });

    const callerNumber = config?.callerId ?? '';

    return this.prisma.sipCall.create({
      data: {
        clubId,
        callerId,
        clientId: dto.clientId,
        callerNumber,
        calleeNumber: dto.calleeNumber,
        direction: 'OUTBOUND',
        status: CallStatus.INITIATED,
        startedAt: new Date(),
      },
      include: CALL_INCLUDE,
    });
  }

  /** Update call status and timing fields. */
  async updateCallStatus(
    clubId: string,
    id: string,
    dto: UpdateCallStatusDto,
  ) {
    await this.ensureCallExists(clubId, id);

    return this.prisma.sipCall.update({
      where: { id },
      data: {
        status: dto.status,
        ...(dto.answeredAt ? { answeredAt: new Date(dto.answeredAt) } : {}),
        ...(dto.endedAt ? { endedAt: new Date(dto.endedAt) } : {}),
        ...(dto.duration !== undefined ? { duration: dto.duration } : {}),
      },
      include: CALL_INCLUDE,
    });
  }

  /** Aggregate call statistics for a club, optionally filtered by trainer and date range. */
  async getCallStats(
    clubId: string,
    trainerId?: string,
    dateFrom?: string,
    dateTo?: string,
  ): Promise<CallStats> {
    const where: Prisma.SipCallWhereInput = {
      clubId,
      ...(trainerId ? { callerId: trainerId } : {}),
      ...this.buildDateFilter(dateFrom, dateTo),
    };

    const [totalResult, answeredResult, durationResult] = await Promise.all([
      this.prisma.sipCall.count({ where }),
      this.prisma.sipCall.count({
        where: { ...where, status: CallStatus.ANSWERED },
      }),
      this.prisma.sipCall.aggregate({
        where: { ...where, status: CallStatus.COMPLETED, duration: { not: null } },
        _avg: { duration: true },
      }),
    ]);

    const completedCount = await this.prisma.sipCall.count({
      where: { ...where, status: CallStatus.COMPLETED },
    });

    const answeredCalls = answeredResult + completedCount;
    const missedCalls = totalResult - answeredCalls;

    return {
      totalCalls: totalResult,
      answeredCalls,
      missedCalls,
      averageDuration: Math.round(durationResult._avg.duration ?? 0),
      answeredRate: totalResult > 0
        ? Math.round((answeredCalls / totalResult) * 10000) / 100
        : 0,
    };
  }

  // ─── SIP Config ───────────────────────────────────────────────────

  /** Get SIP configuration for a club. */
  async getConfig(clubId: string) {
    const config = await this.prisma.sipConfig.findUnique({
      where: { clubId },
    });

    if (!config) {
      throw new NotFoundException(
        `SIP configuration not found for this club`,
      );
    }
    return config;
  }

  /** Create or update SIP configuration for a club. */
  async upsertConfig(clubId: string, dto: CreateSipConfigDto | UpdateSipConfigDto) {
    return this.prisma.sipConfig.upsert({
      where: { clubId },
      create: {
        clubId,
        sipServer: (dto as CreateSipConfigDto).sipServer,
        sipPort: dto.sipPort ?? 5060,
        transport: dto.transport ?? 'TLS',
        realm: dto.realm,
        stunServer: dto.stunServer,
        turnServer: dto.turnServer,
        turnUsername: dto.turnUsername,
        turnPassword: dto.turnPassword,
        callerId: (dto as CreateSipConfigDto).callerId,
        codecs: dto.codecs ?? ['opus', 'g711_alaw', 'g711_ulaw'],
      },
      update: {
        ...(dto.sipServer !== undefined ? { sipServer: dto.sipServer } : {}),
        ...(dto.sipPort !== undefined ? { sipPort: dto.sipPort } : {}),
        ...(dto.transport !== undefined ? { transport: dto.transport } : {}),
        ...(dto.realm !== undefined ? { realm: dto.realm } : {}),
        ...(dto.stunServer !== undefined ? { stunServer: dto.stunServer } : {}),
        ...(dto.turnServer !== undefined ? { turnServer: dto.turnServer } : {}),
        ...(dto.turnUsername !== undefined ? { turnUsername: dto.turnUsername } : {}),
        ...(dto.turnPassword !== undefined ? { turnPassword: dto.turnPassword } : {}),
        ...(dto.callerId !== undefined ? { callerId: dto.callerId } : {}),
        ...(dto.codecs !== undefined ? { codecs: dto.codecs } : {}),
        ...((dto as UpdateSipConfigDto).isActive !== undefined
          ? { isActive: (dto as UpdateSipConfigDto).isActive }
          : {}),
      },
    });
  }

  // ─── SIP Account ──────────────────────────────────────────────────

  /** Get a user's SIP account. */
  async getAccount(clubId: string, userId: string) {
    const account = await this.prisma.sipAccount.findFirst({
      where: { clubId, userId },
    });

    if (!account) {
      throw new NotFoundException(
        `SIP account not found for user ${userId}`,
      );
    }
    return account;
  }

  /** Create or update a SIP account for a user. */
  async upsertAccount(
    clubId: string,
    userId: string,
    dto: CreateSipAccountDto | UpdateSipAccountDto | UpdateOwnSipAccountDto,
  ) {
    return this.prisma.sipAccount.upsert({
      where: { userId },
      create: {
        clubId,
        userId,
        sipUsername: (dto as CreateSipAccountDto).sipUsername ?? '',
        sipPassword: (dto as CreateSipAccountDto).sipPassword ?? '',
        displayName: dto.displayName,
      },
      update: {
        ...(('sipUsername' in dto && dto.sipUsername !== undefined)
          ? { sipUsername: dto.sipUsername }
          : {}),
        ...(('sipPassword' in dto && dto.sipPassword !== undefined)
          ? { sipPassword: dto.sipPassword }
          : {}),
        ...(dto.displayName !== undefined ? { displayName: dto.displayName } : {}),
        ...(('isActive' in dto && dto.isActive !== undefined)
          ? { isActive: dto.isActive }
          : {}),
      },
    });
  }

  // ─── Private helpers ──────────────────────────────────────────────

  /** Build date range filter for startedAt. */
  private buildDateFilter(
    dateFrom?: string,
    dateTo?: string,
  ): Prisma.SipCallWhereInput {
    if (!dateFrom && !dateTo) return {};

    const startedAt: Prisma.DateTimeFilter = {};
    if (dateFrom) startedAt.gte = new Date(dateFrom);
    if (dateTo) startedAt.lte = new Date(dateTo);

    return { startedAt };
  }

  /** Ensure a call exists within the club. */
  private async ensureCallExists(clubId: string, id: string) {
    const call = await this.prisma.sipCall.findFirst({
      where: { id, clubId },
    });
    if (!call) {
      throw new NotFoundException(`Call with id ${id} not found`);
    }
    return call;
  }

  /** Ensure a client exists within the club. */
  private async ensureClientExists(
    clubId: string,
    clientId: string,
  ): Promise<void> {
    const count = await this.prisma.client.count({
      where: { id: clientId, clubId },
    });
    if (count === 0) {
      throw new NotFoundException(
        `Client with id ${clientId} not found`,
      );
    }
  }
}
