<?php

namespace objects;

use classes\db;
use classes\department_daily_report_complaints_schema_bootstrap;
use classes\object_property;
use Exception;
use traits\db_object_t;

class department_daily_report_complaints_o extends db
{
    use db_object_t;

    private const CATEGORY_VALUES = [
        'wash_quality',
        'wash_price',
        'damage_paint',
        'damage_mirrors',
        'damage_cables_electronics',
        'damage_plastic_parts',
        'damage_other',
        'service',
        'other',
    ];

    public object_property $department_id;
    public object_property $customer_number;
    public object_property $wash_date;
    public object_property $category;
    public object_property $description;
    public object_property $created_by;
    public object_property $created_at;

    public function structure(): void
    {
        department_daily_report_complaints_schema_bootstrap::ensureTables();
        $this->setTable('department_daily_report_complaints');
    }

    public function getObjectProperties(): void
    {
        $this->department_id = new object_property($this->table, $this->id, 'department_id', 'int', false);
        $this->customer_number = new object_property($this->table, $this->id, 'customer_number', 'int', false);
        $this->wash_date = new object_property($this->table, $this->id, 'wash_date', 'string', false);
        $this->category = new object_property($this->table, $this->id, 'category', 'string', false);
        $this->description = new object_property($this->table, $this->id, 'description', 'string', false);
        $this->created_by = new object_property($this->table, $this->id, 'created_by', 'int', false);
        $this->created_at = new object_property($this->table, $this->id, 'created_at', 'string', false);
    }

    public function objectChanged(): void
    {
        // No additional cache invalidation is needed for complaint rows in v1.
    }

    public function asArray(): array
    {
        $this->requireSelected();

        return [
            'id' => (int)$this->id,
            'department_id' => (int)$this->department_id->value(),
            'customer_number' => $this->customer_number->value() === null ? null : (int)$this->customer_number->value(),
            'wash_date' => $this->wash_date->value() === null ? null : (string)$this->wash_date->value(),
            'category' => $this->category->value() === null ? null : (string)$this->category->value(),
            'description' => (string)$this->description->value(),
            'created_by' => (int)$this->created_by->value(),
            'created_at' => (string)$this->created_at->value(),
        ];
    }

    public function parseComplaint(array $complaint): array
    {
        static $department_name_cache = [];
        static $customer_name_cache = [];
        static $created_by_name_cache = [];

        $department_id = (int)($complaint['department_id'] ?? 0);
        $customer_number = isset($complaint['customer_number']) && $complaint['customer_number'] !== null
            ? (int)$complaint['customer_number']
            : null;
        $wash_date = isset($complaint['wash_date']) && $complaint['wash_date'] !== null
            ? trim((string)$complaint['wash_date'])
            : null;
        $category = isset($complaint['category']) && $complaint['category'] !== null
            ? trim((string)$complaint['category'])
            : null;
        $created_by = (int)($complaint['created_by'] ?? 0);

        if (!array_key_exists($department_id, $department_name_cache)) {
            $department_name_cache[$department_id] = $department_id > 0
                ? (new departments_o())->getDepartmentName($department_id)
                : null;
        }

        if ($customer_number !== null && !array_key_exists($customer_number, $customer_name_cache)) {
            $customer_name_cache[$customer_number] = (new users_o())->getCustomerName($customer_number);
        }

        if (!array_key_exists($created_by, $created_by_name_cache)) {
            $created_by_name_cache[$created_by] = $this->resolveCreatedByName($created_by);
        }

        return [
            'id' => (int)($complaint['id'] ?? 0),
            'department_id' => $department_id,
            'department_name' => $department_name_cache[$department_id] ?? null,
            'customer_number' => $customer_number,
            'customer_name' => $customer_number !== null
                ? ($customer_name_cache[$customer_number] ?? null)
                : null,
            'wash_date' => $wash_date === '' ? null : $wash_date,
            'category' => $category === '' ? null : $category,
            'description' => (string)($complaint['description'] ?? ''),
            'created_by' => $created_by,
            'created_by_name' => $created_by_name_cache[$created_by] ?? null,
            'created_at' => (string)($complaint['created_at'] ?? ''),
        ];
    }

    public function parseComplaints(array $complaints): array
    {
        return array_map(fn (array $complaint): array => $this->parseComplaint($complaint), $complaints);
    }

    /**
     * @throws Exception
     */
    public function addComplaint(
        int $department_id,
        ?int $customer_number,
        string $wash_date,
        string $category,
        string $description,
        int $created_by
    ): self
    {
        $wash_date = trim($wash_date);
        if ($wash_date === '') {
            throw new Exception('Wash date is required');
        }

        $category = trim($category);
        if (!self::isValidCategory($category)) {
            throw new Exception('Invalid complaint category');
        }

        $description = trim($description);
        if ($description === '') {
            throw new Exception('Description is required');
        }

        $this->id = $this->add_object([
            'department_id' => (int)$department_id,
            'customer_number' => $customer_number === null ? null : (int)$customer_number,
            'wash_date' => $wash_date,
            'category' => $category,
            'description' => $description,
            'created_by' => (int)$created_by,
        ]);
        $this->getObjectProperties();
        $this->objectChanged();

        return $this;
    }

    /**
     * @param array<int> $department_ids
     */
    public function countForDepartmentsInRange(array $department_ids, string $date, ?string $date_to = null): int
    {
        global $db;

        $normalized_department_ids = array_values(array_unique(array_filter(
            array_map('intval', $department_ids),
            static fn (int $department_id): bool => $department_id > 0
        )));

        if ($normalized_department_ids === []) {
            return 0;
        }

        if ($date_to === null) {
            $date_to = $date;
        }

        $range_start = date('Y-m-d 00:00:00', strtotime($date));
        $range_end = date('Y-m-d 23:59:59', strtotime($date_to));
        $department_ids_sql = implode(',', $normalized_department_ids);
        $date_sql = $db->escape_string($date);
        $date_to_sql = $db->escape_string($date_to);
        $range_start_sql = $db->escape_string($range_start);
        $range_end_sql = $db->escape_string($range_end);

        $result = $db->query(
            "SELECT COUNT(*) AS total
             FROM department_daily_report_complaints
             WHERE department_id IN ($department_ids_sql)
               AND (
                   (wash_date IS NOT NULL AND wash_date BETWEEN '$date_sql' AND '$date_to_sql')
                   OR
                   (wash_date IS NULL AND created_at BETWEEN '$range_start_sql' AND '$range_end_sql')
               )"
        );
        $row = $db->fetch_assoc($result);

        return (int)($row['total'] ?? 0);
    }

    public static function validCategories(): array
    {
        return self::CATEGORY_VALUES;
    }

    public static function isValidCategory(?string $category): bool
    {
        if ($category === null) {
            return false;
        }

        return in_array(trim($category), self::CATEGORY_VALUES, true);
    }

    private function resolveCreatedByName(int $user_id): ?string
    {
        if ($user_id <= 0) {
            return null;
        }

        $user = (new users_o())->select($user_id);
        if (!$user->exists()) {
            return null;
        }

        $display_name = trim((string)$user->display_name->value());
        return $display_name === '' ? null : $display_name;
    }
}
