<?php

namespace Modules\Operations\Services;

use App\Models\ActivityLogModel;
use App\Models\CompanyModel;
use App\Models\EmployeeModel;
use App\Models\QcAuditChecklistItemModel;
use App\Models\QcAuditChecklistModel;
use App\Models\QcInspectionModel;
use App\Models\QcPerformanceScoreModel;
use App\Models\WorkOrderModel;

class QualityControlService
{
    protected QcInspectionModel $inspections;
    protected QcAuditChecklistModel $checklists;
    protected QcAuditChecklistItemModel $checklistItems;
    protected QcPerformanceScoreModel $scores;
    protected EmployeeModel $employees;
    protected CompanyModel $companies;
    protected WorkOrderModel $workOrders;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->inspections    = new QcInspectionModel();
        $this->checklists     = new QcAuditChecklistModel();
        $this->checklistItems = new QcAuditChecklistItemModel();
        $this->scores         = new QcPerformanceScoreModel();
        $this->employees      = new EmployeeModel();
        $this->companies      = new CompanyModel();
        $this->workOrders     = new WorkOrderModel();
        $this->activity       = new ActivityLogModel();
    }

    // -------------------------------------------------------------------------
    // Inspections
    // -------------------------------------------------------------------------

    public function createInspection(array $input, ?int $actingUserId): array
    {
        $title = trim((string) ($input['title'] ?? ''));
        if ($title === '') {
            return ['success' => false, 'errors' => ['title' => 'Title is required.']];
        }

        $priority = (string) ($input['priority'] ?? 'normal');
        if (! in_array($priority, QcInspectionModel::PRIORITIES, true)) {
            $priority = 'normal';
        }

        $status = (string) ($input['status'] ?? 'planned');
        if (! in_array($status, QcInspectionModel::OPEN_STATUSES, true)) {
            $status = 'planned';
        }

        $result = (string) ($input['overall_result'] ?? 'pending');
        if (! in_array($result, QcInspectionModel::RESULTS, true)) {
            $result = 'pending';
        }

        $inspectorId = $this->nullableActiveEmployeeId($input['inspector_employee_id'] ?? null, 'inspector_employee_id');
        if (is_array($inspectorId)) {
            return $inspectorId;
        }

        $companyId = $this->nullableEntityId($input['company_id'] ?? null, $this->companies, 'company_id', 'Company not found.');
        if (is_array($companyId)) {
            return $companyId;
        }

        $workOrderId = $this->nullableEntityId($input['work_order_id'] ?? null, $this->workOrders, 'work_order_id', 'Work order not found.');
        if (is_array($workOrderId)) {
            return $workOrderId;
        }

        $serviceVisitId = $this->nullablePositiveInt($input['service_visit_id'] ?? null);

        $data = [
            'number'                => $this->inspections->nextNumber(),
            'title'                 => $title,
            'description'           => trim((string) ($input['description'] ?? '')) ?: null,
            'company_id'            => $companyId,
            'work_order_id'         => $workOrderId,
            'service_visit_id'      => $serviceVisitId,
            'inspector_employee_id' => $inspectorId,
            'priority'              => $priority,
            'status'                => $status,
            'overall_result'        => $result,
            'location'              => trim((string) ($input['location'] ?? '')) ?: null,
            'scheduled_date'        => $this->nullableDate($input['scheduled_date'] ?? null),
            'score'                 => $this->nullableDecimal($input['score'] ?? null),
            'notes'                 => trim((string) ($input['notes'] ?? '')) ?: null,
            'created_by'            => $actingUserId,
            'updated_by'            => $actingUserId,
        ];

        if (! $this->inspections->insert($data, true)) {
            return ['success' => false, 'errors' => $this->inspections->errors()];
        }

        $id = (int) $this->inspections->getInsertID();
        $this->activity->log('quality_control', 'created', $actingUserId, "Created inspection #{$id} ({$data['number']})", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function updateInspection(int $id, array $input, ?int $actingUserId): array
    {
        $row = $this->inspections->find($id);
        if (! $row) {
            return ['success' => false, 'errors' => ['id' => 'Inspection not found.']];
        }

        if (! in_array($row['status'], QcInspectionModel::OPEN_STATUSES, true)) {
            return ['success' => false, 'errors' => ['status' => 'Only open inspections can be edited.']];
        }

        $title = trim((string) ($input['title'] ?? ''));
        if ($title === '') {
            return ['success' => false, 'errors' => ['title' => 'Title is required.']];
        }

        $priority = (string) ($input['priority'] ?? $row['priority']);
        if (! in_array($priority, QcInspectionModel::PRIORITIES, true)) {
            $priority = $row['priority'];
        }

        $status = (string) ($input['status'] ?? $row['status']);
        if (! in_array($status, QcInspectionModel::OPEN_STATUSES, true)) {
            $status = $row['status'];
        }

        $result = (string) ($input['overall_result'] ?? $row['overall_result']);
        if (! in_array($result, QcInspectionModel::RESULTS, true)) {
            $result = $row['overall_result'];
        }

        $inspectorId = $this->nullableActiveEmployeeId($input['inspector_employee_id'] ?? null, 'inspector_employee_id');
        if (is_array($inspectorId)) {
            return $inspectorId;
        }

        $companyId = $this->nullableEntityId($input['company_id'] ?? null, $this->companies, 'company_id', 'Company not found.');
        if (is_array($companyId)) {
            return $companyId;
        }

        $workOrderId = $this->nullableEntityId($input['work_order_id'] ?? null, $this->workOrders, 'work_order_id', 'Work order not found.');
        if (is_array($workOrderId)) {
            return $workOrderId;
        }

        $data = [
            'title'                 => $title,
            'description'           => trim((string) ($input['description'] ?? '')) ?: null,
            'company_id'            => $companyId,
            'work_order_id'         => $workOrderId,
            'service_visit_id'      => $this->nullablePositiveInt($input['service_visit_id'] ?? null),
            'inspector_employee_id' => $inspectorId,
            'priority'              => $priority,
            'status'                => $status,
            'overall_result'        => $result,
            'location'              => trim((string) ($input['location'] ?? '')) ?: null,
            'scheduled_date'        => $this->nullableDate($input['scheduled_date'] ?? null),
            'score'                 => $this->nullableDecimal($input['score'] ?? null),
            'notes'                 => trim((string) ($input['notes'] ?? '')) ?: null,
            'updated_by'            => $actingUserId,
        ];

        if (! $this->inspections->update($id, $data)) {
            return ['success' => false, 'errors' => $this->inspections->errors()];
        }

        $this->activity->log('quality_control', 'updated', $actingUserId, "Updated inspection #{$id}", (string) $id);

        return ['success' => true];
    }

    public function completeInspection(int $id, array $input, ?int $actingUserId): array
    {
        $row = $this->inspections->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Inspection not found.'];
        }

        if (! in_array($row['status'], QcInspectionModel::OPEN_STATUSES, true)) {
            return ['success' => false, 'error' => 'Only open inspections can be completed.'];
        }

        $result = (string) ($input['overall_result'] ?? $row['overall_result']);
        if (! in_array($result, QcInspectionModel::RESULTS, true) || $result === 'pending') {
            $result = (string) ($input['overall_result'] ?? 'pass');
            if (! in_array($result, ['pass', 'fail', 'na'], true)) {
                $result = 'pass';
            }
        }

        $update = [
            'status'         => 'completed',
            'overall_result' => $result,
            'completed_at'   => date('Y-m-d H:i:s'),
            'updated_by'     => $actingUserId,
        ];

        if (array_key_exists('score', $input) && $input['score'] !== '' && $input['score'] !== null) {
            $update['score'] = $this->nullableDecimal($input['score']);
        }

        if (! empty($input['notes'])) {
            $update['notes'] = trim((string) $input['notes']);
        }

        $this->inspections->update($id, $update);
        $this->activity->log('quality_control', 'completed', $actingUserId, "Completed inspection #{$id}", (string) $id);

        return ['success' => true];
    }

    public function cancelInspection(int $id, ?int $actingUserId): array
    {
        $row = $this->inspections->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Inspection not found.'];
        }

        if (! in_array($row['status'], QcInspectionModel::OPEN_STATUSES, true)) {
            return ['success' => false, 'error' => 'Only open inspections can be cancelled.'];
        }

        $this->inspections->update($id, [
            'status'     => 'cancelled',
            'updated_by' => $actingUserId,
        ]);
        $this->activity->log('quality_control', 'cancelled', $actingUserId, "Cancelled inspection #{$id}", (string) $id);

        return ['success' => true];
    }

    public function deleteInspection(int $id, ?int $actingUserId): array
    {
        $row = $this->inspections->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Inspection not found.'];
        }

        if (! in_array($row['status'], ['planned', 'cancelled'], true)) {
            return ['success' => false, 'error' => 'Only planned or cancelled inspections can be deleted.'];
        }

        $this->inspections->delete($id);
        $this->activity->log('quality_control', 'deleted', $actingUserId, "Deleted inspection #{$id}", (string) $id);

        return ['success' => true];
    }

    // -------------------------------------------------------------------------
    // Audit checklists
    // -------------------------------------------------------------------------

    public function createChecklist(array $input, ?int $actingUserId): array
    {
        $name = trim((string) ($input['name'] ?? ''));
        if ($name === '') {
            return ['success' => false, 'errors' => ['name' => 'Name is required.']];
        }

        $status = (string) ($input['status'] ?? 'active');
        if (! in_array($status, QcAuditChecklistModel::STATUSES, true)) {
            $status = 'active';
        }

        $data = [
            'number'      => $this->checklists->nextNumber(),
            'name'        => $name,
            'code'        => trim((string) ($input['code'] ?? '')) ?: null,
            'description' => trim((string) ($input['description'] ?? '')) ?: null,
            'status'      => $status,
            'created_by'  => $actingUserId,
            'updated_by'  => $actingUserId,
        ];

        if (! $this->checklists->insert($data, true)) {
            return ['success' => false, 'errors' => $this->checklists->errors()];
        }

        $id = (int) $this->checklists->getInsertID();
        $this->activity->log('quality_control', 'checklist_created', $actingUserId, "Created audit checklist #{$id} ({$data['number']})", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function updateChecklist(int $id, array $input, ?int $actingUserId): array
    {
        $row = $this->checklists->find($id);
        if (! $row) {
            return ['success' => false, 'errors' => ['id' => 'Checklist not found.']];
        }

        $name = trim((string) ($input['name'] ?? ''));
        if ($name === '') {
            return ['success' => false, 'errors' => ['name' => 'Name is required.']];
        }

        $status = (string) ($input['status'] ?? $row['status']);
        if (! in_array($status, QcAuditChecklistModel::STATUSES, true)) {
            $status = $row['status'];
        }

        $data = [
            'name'        => $name,
            'code'        => trim((string) ($input['code'] ?? '')) ?: null,
            'description' => trim((string) ($input['description'] ?? '')) ?: null,
            'status'      => $status,
            'updated_by'  => $actingUserId,
        ];

        if (! $this->checklists->update($id, $data)) {
            return ['success' => false, 'errors' => $this->checklists->errors()];
        }

        $this->activity->log('quality_control', 'checklist_updated', $actingUserId, "Updated audit checklist #{$id}", (string) $id);

        return ['success' => true];
    }

    public function deleteChecklist(int $id, ?int $actingUserId): array
    {
        $row = $this->checklists->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Checklist not found.'];
        }

        $this->checklistItems->where('checklist_id', $id)->delete();
        $this->checklists->delete($id);
        $this->activity->log('quality_control', 'checklist_deleted', $actingUserId, "Deleted audit checklist #{$id}", (string) $id);

        return ['success' => true];
    }

    public function addChecklistItem(int $checklistId, array $input, ?int $actingUserId): array
    {
        $checklist = $this->checklists->find($checklistId);
        if (! $checklist) {
            return ['success' => false, 'errors' => ['checklist_id' => 'Checklist not found.']];
        }

        $title = trim((string) ($input['title'] ?? ''));
        if ($title === '') {
            return ['success' => false, 'errors' => ['title' => 'Checklist item title is required.']];
        }

        $this->checklistItems->insert([
            'checklist_id' => $checklistId,
            'title'        => $title,
            'is_required'  => ! empty($input['is_required']) ? 1 : 0,
            'sort_order'   => $this->checklistItems->nextSortOrder($checklistId),
        ]);

        $this->activity->log(
            'quality_control',
            'checklist_item_added',
            $actingUserId,
            "Added item to audit checklist #{$checklistId}",
            (string) $checklistId
        );

        return ['success' => true];
    }

    public function removeChecklistItem(int $itemId, ?int $actingUserId): array
    {
        $item = $this->checklistItems->find($itemId);
        if (! $item) {
            return ['success' => false, 'error' => 'Checklist item not found.'];
        }

        $this->checklistItems->delete($itemId);
        $this->activity->log(
            'quality_control',
            'checklist_item_removed',
            $actingUserId,
            "Removed item #{$itemId} from audit checklist #{$item['checklist_id']}",
            (string) $item['checklist_id']
        );

        return ['success' => true, 'checklist_id' => (int) $item['checklist_id']];
    }

    public function toggleChecklistItemRequired(int $itemId, ?int $actingUserId): array
    {
        $item = $this->checklistItems->find($itemId);
        if (! $item) {
            return ['success' => false, 'error' => 'Checklist item not found.'];
        }

        $newRequired = empty($item['is_required']) ? 1 : 0;
        $this->checklistItems->update($itemId, ['is_required' => $newRequired]);
        $this->activity->log(
            'quality_control',
            'checklist_item_toggled',
            $actingUserId,
            "Toggled required on item #{$itemId} for checklist #{$item['checklist_id']}",
            (string) $item['checklist_id']
        );

        return ['success' => true, 'checklist_id' => (int) $item['checklist_id']];
    }

    // -------------------------------------------------------------------------
    // Performance scores
    // -------------------------------------------------------------------------

    public function createScore(array $input, ?int $actingUserId): array
    {
        $metricCode = trim((string) ($input['metric_code'] ?? ''));
        $metricName = trim((string) ($input['metric_name'] ?? ''));
        if ($metricCode === '') {
            return ['success' => false, 'errors' => ['metric_code' => 'Metric code is required.']];
        }
        if ($metricName === '') {
            return ['success' => false, 'errors' => ['metric_name' => 'Metric name is required.']];
        }

        $subjectType = (string) ($input['subject_type'] ?? 'employee');
        if (! in_array($subjectType, QcPerformanceScoreModel::SUBJECT_TYPES, true)) {
            return ['success' => false, 'errors' => ['subject_type' => 'Invalid subject type.']];
        }

        $subjectId = (int) ($input['subject_id'] ?? 0);
        if ($subjectId < 1) {
            return ['success' => false, 'errors' => ['subject_id' => 'Subject is required.']];
        }

        $scoredBy = $this->nullableActiveEmployeeId($input['scored_by'] ?? null, 'scored_by');
        if (is_array($scoredBy)) {
            return $scoredBy;
        }

        $maxScore = (float) ($input['max_score'] ?? 100);
        if ($maxScore <= 0) {
            $maxScore = 100;
        }

        $score = (float) ($input['score'] ?? 0);

        $data = [
            'number'        => $this->scores->nextNumber(),
            'subject_type'  => $subjectType,
            'subject_id'    => $subjectId,
            'subject_label' => trim((string) ($input['subject_label'] ?? '')) ?: null,
            'metric_code'   => $metricCode,
            'metric_name'   => $metricName,
            'score'         => $score,
            'max_score'     => $maxScore,
            'period_start'  => $this->nullableDate($input['period_start'] ?? null),
            'period_end'    => $this->nullableDate($input['period_end'] ?? null),
            'status'        => 'draft',
            'notes'         => trim((string) ($input['notes'] ?? '')) ?: null,
            'scored_by'     => $scoredBy,
            'created_by'    => $actingUserId,
            'updated_by'    => $actingUserId,
        ];

        if (! $this->scores->insert($data, true)) {
            return ['success' => false, 'errors' => $this->scores->errors()];
        }

        $id = (int) $this->scores->getInsertID();
        $this->activity->log('quality_control', 'score_created', $actingUserId, "Created performance score #{$id} ({$data['number']})", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function updateScore(int $id, array $input, ?int $actingUserId): array
    {
        $row = $this->scores->find($id);
        if (! $row) {
            return ['success' => false, 'errors' => ['id' => 'Performance score not found.']];
        }

        if ($row['status'] !== 'draft') {
            return ['success' => false, 'errors' => ['status' => 'Only draft scores can be edited.']];
        }

        $metricCode = trim((string) ($input['metric_code'] ?? ''));
        $metricName = trim((string) ($input['metric_name'] ?? ''));
        if ($metricCode === '') {
            return ['success' => false, 'errors' => ['metric_code' => 'Metric code is required.']];
        }
        if ($metricName === '') {
            return ['success' => false, 'errors' => ['metric_name' => 'Metric name is required.']];
        }

        $subjectType = (string) ($input['subject_type'] ?? $row['subject_type']);
        if (! in_array($subjectType, QcPerformanceScoreModel::SUBJECT_TYPES, true)) {
            $subjectType = $row['subject_type'];
        }

        $subjectId = (int) ($input['subject_id'] ?? $row['subject_id']);
        if ($subjectId < 1) {
            return ['success' => false, 'errors' => ['subject_id' => 'Subject is required.']];
        }

        $scoredBy = $this->nullableActiveEmployeeId($input['scored_by'] ?? null, 'scored_by');
        if (is_array($scoredBy)) {
            return $scoredBy;
        }

        $maxScore = (float) ($input['max_score'] ?? $row['max_score']);
        if ($maxScore <= 0) {
            $maxScore = (float) $row['max_score'];
        }

        $data = [
            'subject_type'  => $subjectType,
            'subject_id'    => $subjectId,
            'subject_label' => trim((string) ($input['subject_label'] ?? '')) ?: null,
            'metric_code'   => $metricCode,
            'metric_name'   => $metricName,
            'score'         => (float) ($input['score'] ?? $row['score']),
            'max_score'     => $maxScore,
            'period_start'  => $this->nullableDate($input['period_start'] ?? null),
            'period_end'    => $this->nullableDate($input['period_end'] ?? null),
            'notes'         => trim((string) ($input['notes'] ?? '')) ?: null,
            'scored_by'     => $scoredBy,
            'updated_by'    => $actingUserId,
        ];

        if (! $this->scores->update($id, $data)) {
            return ['success' => false, 'errors' => $this->scores->errors()];
        }

        $this->activity->log('quality_control', 'score_updated', $actingUserId, "Updated performance score #{$id}", (string) $id);

        return ['success' => true];
    }

    public function publishScore(int $id, ?int $actingUserId): array
    {
        $row = $this->scores->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Performance score not found.'];
        }

        if ($row['status'] !== 'draft') {
            return ['success' => false, 'error' => 'Only draft scores can be published.'];
        }

        $this->scores->update($id, [
            'status'     => 'published',
            'updated_by' => $actingUserId,
        ]);
        $this->activity->log('quality_control', 'score_published', $actingUserId, "Published performance score #{$id}", (string) $id);

        return ['success' => true];
    }

    public function deleteScore(int $id, ?int $actingUserId): array
    {
        $row = $this->scores->find($id);
        if (! $row) {
            return ['success' => false, 'error' => 'Performance score not found.'];
        }

        if ($row['status'] !== 'draft') {
            return ['success' => false, 'error' => 'Only draft scores can be deleted.'];
        }

        $this->scores->delete($id);
        $this->activity->log('quality_control', 'score_deleted', $actingUserId, "Deleted performance score #{$id}", (string) $id);

        return ['success' => true];
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    /**
     * @return int|null|array{success:false,errors:array}
     */
    protected function nullableActiveEmployeeId(mixed $value, string $field = 'employee_id'): int|null|array
    {
        $id = (int) $value;
        if ($id < 1) {
            return null;
        }

        $employee = $this->employees->find($id);
        if (! $employee || ($employee['status'] ?? '') !== 'active') {
            return ['success' => false, 'errors' => [$field => 'Select an active employee.']];
        }

        return $id;
    }

    /**
     * @return int|null|array{success:false,errors:array}
     */
    protected function nullableEntityId(mixed $value, $model, string $field, string $error): int|null|array
    {
        $id = (int) $value;
        if ($id < 1) {
            return null;
        }

        if (! $model->find($id)) {
            return ['success' => false, 'errors' => [$field => $error]];
        }

        return $id;
    }

    protected function nullablePositiveInt(mixed $value): ?int
    {
        $id = (int) $value;

        return $id > 0 ? $id : null;
    }

    protected function nullableDate(mixed $value): ?string
    {
        $value = trim((string) $value);

        return $value !== '' ? $value : null;
    }

    protected function nullableDecimal(mixed $value): ?float
    {
        $value = trim((string) $value);
        if ($value === '') {
            return null;
        }

        return (float) $value;
    }
}
