<?php

declare(strict_types=1);

namespace App\Repositories;

final class AppointmentRepository extends BaseRepository
{
    public function listForTenant(int $tenantId, string $from, string $to, ?int $instructorId = null): array
    {
        $sql = 'SELECT a.*, s.first_name AS student_first_name, s.last_name AS student_last_name, s.needs_glasses AS student_needs_glasses,
                       i.first_name AS instructor_first_name, i.last_name AS instructor_last_name,
                       lt.name AS lesson_type_name, lt.color AS lesson_type_color, lt.is_rest_relevant,
                       (
                           SELECT au.requirement_key
                           FROM appointment_units au
                           WHERE au.appointment_id = a.id
                           ORDER BY au.id
                           LIMIT 1
                       ) AS counted_requirement_key
                FROM appointments a
                JOIN instructors i ON i.id = a.instructor_id
                LEFT JOIN students s ON s.id = a.student_id
                JOIN lesson_types lt ON lt.id = a.lesson_type_id
                WHERE a.tenant_id = :tenant_id
                  AND a.start_at < :to
                  AND a.end_at > :from';
        $params = [
            'tenant_id' => $tenantId,
            'from' => $from,
            'to' => $to,
        ];

        if ($instructorId !== null) {
            $sql .= ' AND a.instructor_id = :instructor_id';
            $params['instructor_id'] = $instructorId;
        }

        $sql .= ' ORDER BY a.start_at';
        $statement = $this->db->prepare($sql);
        $statement->execute($params);

        return $statement->fetchAll();
    }

    public function find(int $tenantId, int $id): ?array
    {
        $statement = $this->db->prepare('SELECT * FROM appointments WHERE tenant_id = :tenant_id AND id = :id');
        $statement->execute(['tenant_id' => $tenantId, 'id' => $id]);
        $appointment = $statement->fetch() ?: null;

        if ($appointment !== null) {
            $appointment['units_breakdown'] = $this->units((int) $appointment['id']);
        }

        return $appointment;
    }

    public function create(int $tenantId, array $data, int $actorId): array
    {
        $timestamp = $this->now();
        $statement = $this->db->prepare(
            'INSERT INTO appointments
            (tenant_id, student_id, instructor_id, lesson_type_id, title, category, start_at, end_at, units, status, notes, warning_acknowledged, created_by, updated_by, created_at, updated_at)
            VALUES
            (:tenant_id, :student_id, :instructor_id, :lesson_type_id, :title, :category, :start_at, :end_at, :units, :status, :notes, :warning_acknowledged, :created_by, :updated_by, :created_at, :updated_at)'
        );
        $statement->execute([
            'tenant_id' => $tenantId,
            'student_id' => $data['student_id'] ?: null,
            'instructor_id' => $data['instructor_id'],
            'lesson_type_id' => $data['lesson_type_id'],
            'title' => $data['title'],
            'category' => $data['category'],
            'start_at' => $data['start_at'],
            'end_at' => $data['end_at'],
            'units' => (int) $data['units'],
            'status' => $data['status'],
            'notes' => $data['notes'] ?? '',
            'warning_acknowledged' => !empty($data['warning_acknowledged']) ? 1 : 0,
            'created_by' => $actorId,
            'updated_by' => $actorId,
            'created_at' => $timestamp,
            'updated_at' => $timestamp,
        ]);

        $appointmentId = (int) $this->db->lastInsertId();
        $this->replaceUnits($appointmentId, $data['units_breakdown'] ?? []);

        return $this->find($tenantId, $appointmentId);
    }

    public function update(int $tenantId, int $id, array $data, int $actorId): ?array
    {
        $statement = $this->db->prepare(
            'UPDATE appointments
             SET student_id = :student_id, instructor_id = :instructor_id, lesson_type_id = :lesson_type_id,
                 title = :title, category = :category, start_at = :start_at, end_at = :end_at, units = :units,
                 status = :status, notes = :notes, warning_acknowledged = :warning_acknowledged,
                 updated_by = :updated_by, updated_at = :updated_at
             WHERE tenant_id = :tenant_id AND id = :id'
        );
        $statement->execute([
            'tenant_id' => $tenantId,
            'id' => $id,
            'student_id' => $data['student_id'] ?: null,
            'instructor_id' => $data['instructor_id'],
            'lesson_type_id' => $data['lesson_type_id'],
            'title' => $data['title'],
            'category' => $data['category'],
            'start_at' => $data['start_at'],
            'end_at' => $data['end_at'],
            'units' => (int) $data['units'],
            'status' => $data['status'],
            'notes' => $data['notes'] ?? '',
            'warning_acknowledged' => !empty($data['warning_acknowledged']) ? 1 : 0,
            'updated_by' => $actorId,
            'updated_at' => $this->now(),
        ]);

        $this->replaceUnits($id, $data['units_breakdown'] ?? []);

        return $this->find($tenantId, $id);
    }

    public function delete(int $tenantId, int $id): void
    {
        $statement = $this->db->prepare('DELETE FROM appointments WHERE tenant_id = :tenant_id AND id = :id');
        $statement->execute(['tenant_id' => $tenantId, 'id' => $id]);
    }

    public function units(int $appointmentId): array
    {
        $statement = $this->db->prepare('SELECT * FROM appointment_units WHERE appointment_id = :appointment_id');
        $statement->execute(['appointment_id' => $appointmentId]);

        return $statement->fetchAll();
    }

    public function overlappingForInstructor(int $tenantId, int $instructorId, string $from, string $to, ?int $ignoreId = null): array
    {
        $sql = 'SELECT * FROM appointments
                WHERE tenant_id = :tenant_id AND instructor_id = :instructor_id
                  AND start_at < :to AND end_at > :from';
        $params = [
            'tenant_id' => $tenantId,
            'instructor_id' => $instructorId,
            'from' => $from,
            'to' => $to,
        ];

        if ($ignoreId !== null) {
            $sql .= ' AND id != :ignore_id';
            $params['ignore_id'] = $ignoreId;
        }

        $statement = $this->db->prepare($sql);
        $statement->execute($params);

        return $statement->fetchAll();
    }

    public function overlappingForStudent(int $tenantId, int $studentId, string $from, string $to, ?int $ignoreId = null): array
    {
        $sql = 'SELECT * FROM appointments
                WHERE tenant_id = :tenant_id AND student_id = :student_id
                  AND start_at < :to AND end_at > :from';
        $params = [
            'tenant_id' => $tenantId,
            'student_id' => $studentId,
            'from' => $from,
            'to' => $to,
        ];

        if ($ignoreId !== null) {
            $sql .= ' AND id != :ignore_id';
            $params['ignore_id'] = $ignoreId;
        }

        $statement = $this->db->prepare($sql);
        $statement->execute($params);

        return $statement->fetchAll();
    }

    public function lastRestRelevantForInstructor(int $tenantId, int $instructorId, string $beforeStart, ?int $ignoreId = null): ?array
    {
        $sql = 'SELECT a.*, lt.name AS lesson_type_name
                FROM appointments a
                JOIN lesson_types lt ON lt.id = a.lesson_type_id
                WHERE a.tenant_id = :tenant_id
                  AND a.instructor_id = :instructor_id
                  AND lt.is_rest_relevant = 1
                  AND a.end_at <= :before_start';
        $params = [
            'tenant_id' => $tenantId,
            'instructor_id' => $instructorId,
            'before_start' => $beforeStart,
        ];

        if ($ignoreId !== null) {
            $sql .= ' AND a.id != :ignore_id';
            $params['ignore_id'] = $ignoreId;
        }

        $sql .= ' ORDER BY a.end_at DESC LIMIT 1';
        $statement = $this->db->prepare($sql);
        $statement->execute($params);

        return $statement->fetch() ?: null;
    }

    public function hasRestRelevantAfterStartOnDay(int $tenantId, int $instructorId, string $dayStart, string $dayEnd, string $startAt): bool
    {
        $statement = $this->db->prepare(
            'SELECT 1
             FROM appointments a
             JOIN lesson_types lt ON lt.id = a.lesson_type_id
             WHERE a.tenant_id = :tenant_id
               AND a.instructor_id = :instructor_id
               AND lt.is_rest_relevant = 1
               AND a.start_at < :day_end
               AND a.end_at > :day_start
               AND a.end_at > :start_at
             LIMIT 1'
        );
        $statement->execute([
            'tenant_id' => $tenantId,
            'instructor_id' => $instructorId,
            'day_start' => $dayStart,
            'day_end' => $dayEnd,
            'start_at' => $startAt,
        ]);

        return (bool) $statement->fetchColumn();
    }

    public function nextForTenant(int $tenantId, string $from, ?int $instructorId = null): ?array
    {
        $sql = 'SELECT a.*, s.first_name AS student_first_name, s.last_name AS student_last_name,
                       i.first_name AS instructor_first_name, i.last_name AS instructor_last_name,
                       lt.name AS lesson_type_name
                FROM appointments a
                JOIN instructors i ON i.id = a.instructor_id
                LEFT JOIN students s ON s.id = a.student_id
                JOIN lesson_types lt ON lt.id = a.lesson_type_id
                WHERE a.tenant_id = :tenant_id
                  AND a.start_at >= :from';
        $params = [
            'tenant_id' => $tenantId,
            'from' => $from,
        ];

        if ($instructorId !== null) {
            $sql .= ' AND a.instructor_id = :instructor_id';
            $params['instructor_id'] = $instructorId;
        }

        $sql .= ' ORDER BY a.start_at ASC LIMIT 1';
        $statement = $this->db->prepare($sql);
        $statement->execute($params);

        return $statement->fetch() ?: null;
    }

    private function replaceUnits(int $appointmentId, array $rows): void
    {
        $delete = $this->db->prepare('DELETE FROM appointment_units WHERE appointment_id = :appointment_id');
        $delete->execute(['appointment_id' => $appointmentId]);

        $insert = $this->db->prepare(
            'INSERT INTO appointment_units (appointment_id, requirement_key, label, units_counted, created_at)
             VALUES (:appointment_id, :requirement_key, :label, :units_counted, :created_at)'
        );

        foreach ($rows as $row) {
            if ((int) ($row['units_counted'] ?? 0) <= 0) {
                continue;
            }

            $insert->execute([
                'appointment_id' => $appointmentId,
                'requirement_key' => $row['requirement_key'] ?? null,
                'label' => $row['label'] ?? null,
                'units_counted' => (int) $row['units_counted'],
                'created_at' => $this->now(),
            ]);
        }
    }
}
