<?php

namespace App\Services;

use App\Config\DatasourceConfig;
use App\Models\ActivityLogType;
use App\Models\TicketAssignmentModel;
use App\Repositories\SlaRepo;
use App\Repositories\TeknisiRepo;
use App\Repositories\TicketRepo;
use PDO;

class TicketService
{
  private TicketRepo $ticketRepo;
  private AttachmentService $attachmentService;
  private SlaRepo $slaRepo;
  private TicketAssignmentService $ticketAssignmentService;
  private TeknisiRepo $teknisiRepo;

  public function __construct(
    private ActivityLogService $activityLogService,
    private StatusTicketService $statusTicketService
  ) {
    $this->ticketRepo = new TicketRepo();
    $this->attachmentService = new AttachmentService();
    $this->slaRepo = new SlaRepo();
    $this->ticketAssignmentService = new TicketAssignmentService();
    $this->teknisiRepo = new TeknisiRepo();
  }

  public function findAll()
  {
    return $this->ticketRepo->findAll();
  }

  public function findByFilter(array $filter)
  {
    return $this->ticketRepo->findBy(
      $filter['keyword'],
      $filter['id_status'],
      $filter['id_prioritas'],
      $filter['id_kategori'],
      $filter['id_sla']
    );
  }

  public function findById(int $id)
  {
    return $this->ticketRepo->findById($id);
  }

  public function findByUser(int $userId)
  {
    return $this->ticketRepo->findByUser($userId);
  }

  public function findByStatus(int $statusId)
  {
    return $this->ticketRepo->findByStatus($statusId);
  }

  public function create(
    int $userId,
    int $kategoriId,
    int $prioritasId,
    int $slaId,
    int $statusId,
    string $judul,
    ?string $deskripsi,
    ?array $attachments
  ) {

    $connection = (new DatasourceConfig())->connect();

    $uploadedFiles = [];

    try {

      $connection->beginTransaction();

      if ($slaId) {
        $sla = $this->slaRepo->findById($slaId);
        $deadline = $this->generateDeadline($sla->getWaktuPenyelesaianJam());
      }


      $ticketId = $this->ticketRepo->save(
        connection: $connection,
        userId: $userId,
        kategoriId: $kategoriId,
        prioritasId: $prioritasId,
        slaId: $slaId,
        statusId: $statusId,
        judul: $judul,
        deskripsi: $deskripsi,
        deadline: $deadline ?? null,
        nomorTiket: $this->generateNomorTiket($connection)
      );

      if ($attachments) {

        $uploadedFiles = $this->attachmentService->upload(
          connection: $connection,
          ticketId: $ticketId,
          userId: $userId,
          files: $attachments
        );
      }

      $this->activityLogService->create(
        connection: $connection,
        targetUserId: $userId,
        ticketId: $ticketId,
        userId: $userId,
        status: $this->statusTicketService->findById($statusId)->getNamaStatus(),
        log: ActivityLogType::CREATE,
      );

      $connection->commit();
    } catch (\Throwable $e) {

      $connection->rollBack();

      foreach ($uploadedFiles as $file) {

        if (file_exists($file)) {
          unlink($file);
        }
      }

      throw $e;
    }
  }

  public function update(
    int $id,
    int $kategoriId,
    int $prioritasId,
    int $slaId,
    int $statusId,
    string $judul,
    ?string $deskripsi = null,
    ?string $deadline = null,
    bool $isClosed = false
  ) {

    $ticket = $this->ticketRepo->findById($id);

    if (empty($ticket)) {
      throw new \Exception('Tiket tidak ditemukan.');
    }

    $this->isValid(
      $kategoriId,
      $prioritasId,
      $slaId,
      $statusId,
      $judul
    );

    return $this->ticketRepo->update(
      $id,
      $kategoriId,
      $prioritasId,
      $slaId,
      $statusId,
      $judul,
      $deskripsi,
      $deadline,
      $isClosed
    );
  }

  public function delete(int $id)
  {
    $ticket = $this->ticketRepo->findById($id);

    if (empty($ticket)) {
      throw new \Exception('Tiket tidak ditemukan.');
    }

    return $this->ticketRepo->delete($id);
  }

  // ===========================
  // Validation
  // ===========================

  private function validate(
    int $kategoriId,
    int $prioritasId,
    int $slaId,
    int $statusId,
    string $judul
  ): array {

    $errors = [];

    if ($kategoriId <= 0) {
      $errors['kategori'] = 'Kategori wajib dipilih.';
    }

    if ($prioritasId <= 0) {
      $errors['prioritas'] = 'Prioritas wajib dipilih.';
    }

    if ($slaId <= 0) {
      $errors['sla'] = 'SLA wajib dipilih.';
    }

    if ($statusId <= 0) {
      $errors['status'] = 'Status tiket wajib dipilih.';
    }

    if (trim($judul) === '') {
      $errors['judul'] = 'Judul tiket wajib diisi.';
    }

    return $errors;
  }

  private function isValid(
    int $kategoriId,
    int $prioritasId,
    int $slaId,
    int $statusId,
    string $judul
  ): void {

    $errors = $this->validate(
      $kategoriId,
      $prioritasId,
      $slaId,
      $statusId,
      $judul
    );

    if (count($errors) > 0) {
      throw new \Exception(implode(', ', $errors));
    }
  }

  private function generateNomorTiket(PDO $connection): string
  {
    $nextNumber = $this->ticketRepo->getLastTicketNumber($connection);

    return sprintf(
      'TKT-%s-%05d',
      date('Ymd'),
      $nextNumber
    );
  }

  private function generateDeadline(int $resolutionTime): string
  {
    return date(
      'Y-m-d H:i:s',
      strtotime("+{$resolutionTime} hours")
    );
  }

  // update status
  public function updateStatus(int $id, int $statusId, int $userId)
  {

    $status = $this->statusTicketService->findById($statusId)->getNamaStatus();

    $connection = (new DatasourceConfig())->connect();
    $connection->beginTransaction();
    $logType = ActivityLogType::UPDATE_STATUS;
    $close = false;

    $assignmentStatus = null;
    if ($status === 'IN PROGRESS') {
      $assignmentStatus = 'DIPROSES';
    }

    if ($status === 'SOLVED') {
      $assignmentStatus = 'SELESAI';
      $logType = ActivityLogType::SOLVED;
      $close = true;
    }

    if ($status === 'CANCELED') {
      $assignmentStatus = 'SELESAI';
      $logType = ActivityLogType::CANCELED;
      $close = true;
    }

    if ($close) {
      $this->ticketRepo->close($id, $statusId, $connection);
    } else {
      $this->ticketRepo->updateStatus($id, $statusId, $connection);
    }

    $assignment = $this->ticketAssignmentService->findByTicketId($id);
    if ($assignmentStatus !== null && $assignment !== null) {
      $this->ticketAssignmentService->update($id, $assignment->getTeknisiId(), $assignmentStatus, $assignment->getCatatan(), $close);
    }

    $this->activityLogService->create(
      connection: $connection,
      targetUserId: $userId,
      ticketId: $id,
      userId: $userId,
      status: $status,
      log: $logType,
    );

    $connection->commit();
  }

  public function updateSlaTicket(
    int $idTicket,
    int $idSla,
    int $idPrioritas
  ): void {

    $deadline = $this->generateDeadline($idSla);

    $this->ticketRepo->updateSlaTicket(
      $idTicket,
      $idSla,
      $idPrioritas,
      $deadline
    );
  }

  public function findByTeknisiId(int $userId)
  {

    $id = $this->teknisiRepo->findByUserId($userId)->getId();
    $assignments = $this->ticketAssignmentService->findByTeknisiId($id);

    // ticket id from assignment
    $ticketIds = array_map(
      fn(TicketAssignmentModel $assignment) => $assignment->getTicketId(),
      $assignments
    );

    // find by ticket id in
    return $this->ticketRepo->findByTicketIdIn($ticketIds);
  }
}
