<?php

namespace Modules\Contracts\Services;

use App\Models\ActivityLogModel;
use App\Models\CompanyModel;
use App\Models\ContractEmployeeModel;
use App\Models\ContractLineModel;
use App\Models\ContractMilestoneModel;
use App\Models\ContractModel;
use App\Models\EmployeeModel;

class ContractService
{
    protected ContractModel $contracts;
    protected ContractLineModel $lines;
    protected ContractMilestoneModel $milestones;
    protected ContractEmployeeModel $assignments;
    protected CompanyModel $companies;
    protected EmployeeModel $employees;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->contracts   = new ContractModel();
        $this->lines       = new ContractLineModel();
        $this->milestones  = new ContractMilestoneModel();
        $this->assignments = new ContractEmployeeModel();
        $this->companies   = new CompanyModel();
        $this->employees   = new EmployeeModel();
        $this->activity    = new ActivityLogModel();
    }

    public function create(array $input, ?int $actingUserId): array
    {
        $validated = $this->validateAndMap($input);
        if (! $validated['success']) {
            return $validated;
        }

        $data = $validated['data'];
        $data['number']     = $this->contracts->nextNumber();
        $data['created_by'] = $actingUserId;
        $data['updated_by'] = $actingUserId;

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

        $id = (int) $this->contracts->getInsertID();
        $this->syncChildren($id, $data['contract_type'], $input);

        $this->activity->log('contracts', 'created', $actingUserId, "Created contract #{$id} ({$data['number']})", (string) $id);

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

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

        $validated = $this->validateAndMap($input, $id);
        if (! $validated['success']) {
            return $validated;
        }

        $data = $validated['data'];
        $data['updated_by'] = $actingUserId;
        // Keep existing number; do not allow type change away from company type (already enforced)
        unset($data['number']);

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

        $this->syncChildren($id, $data['contract_type'], $input);
        $this->activity->log('contracts', 'updated', $actingUserId, "Updated contract #{$id}", (string) $id);

        return ['success' => true];
    }

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

        $this->lines->where('contract_id', $id)->delete();
        $this->milestones->where('contract_id', $id)->delete();
        $this->assignments->where('contract_id', $id)->delete();
        $this->contracts->delete($id);
        $this->activity->log('contracts', 'deleted', $actingUserId, "Deleted contract #{$id}", (string) $id);

        return ['success' => true];
    }

    protected function validateAndMap(array $input, ?int $excludeId = null): array
    {
        $companyId = (int) ($input['company_id'] ?? 0);
        $company   = $this->companies->find($companyId);
        if (! $company) {
            return ['success' => false, 'errors' => ['company_id' => 'Customer not found.']];
        }

        $customerType = $company['customer_type'] ?? null;
        if (! in_array($customerType, ['product', 'service', 'project'], true)) {
            return ['success' => false, 'errors' => ['company_id' => 'Customer must have a type (product, service, or project) before creating a contract.']];
        }

        $contractType = $input['contract_type'] ?? $customerType;
        if ($contractType !== $customerType) {
            return ['success' => false, 'errors' => ['contract_type' => 'Contract type must match the customer type (' . $customerType . ').']];
        }

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

        $status = $input['status'] ?? 'draft';
        if (! in_array($status, ['draft', 'active', 'expired', 'terminated', 'cancelled'], true)) {
            $status = 'draft';
        }

        $data = [
            'title'         => $title,
            'company_id'    => $companyId,
            'contract_type' => $contractType,
            'status'        => $status,
            'start_date'    => trim((string) ($input['start_date'] ?? '')) ?: null,
            'end_date'      => trim((string) ($input['end_date'] ?? '')) ?: null,
            'value'         => (float) ($input['value'] ?? 0),
            'notes'         => trim((string) ($input['notes'] ?? '')) ?: null,
            'total_hours'   => null,
            'hourly_rate'   => null,
            'location'      => null,
            'project_name'  => null,
            'project_description' => null,
        ];

        if ($contractType === 'service') {
            $hours = $input['total_hours'] ?? '';
            $rate  = $input['hourly_rate'] ?? '';
            $location = trim((string) ($input['location'] ?? ''));

            if ($hours === '' || (float) $hours <= 0) {
                return ['success' => false, 'errors' => ['total_hours' => 'Total hours are required for service contracts.']];
            }
            if ($rate === '' || (float) $rate < 0) {
                return ['success' => false, 'errors' => ['hourly_rate' => 'Hourly rate is required for service contracts.']];
            }
            if ($location === '') {
                return ['success' => false, 'errors' => ['location' => 'Location is required for service contracts.']];
            }

            $data['total_hours'] = (float) $hours;
            $data['hourly_rate'] = (float) $rate;
            $data['location']    = $location;

            if (! isset($input['value']) || $input['value'] === '') {
                $data['value'] = round($data['total_hours'] * $data['hourly_rate'], 2);
            }

            $assignments = $input['employees'] ?? [];
            if (! is_array($assignments) || count($this->normalizeEmployees($assignments)) === 0) {
                return ['success' => false, 'errors' => ['employees' => 'Assign at least one employee for service contracts.']];
            }
        }

        if ($contractType === 'project') {
            $projectName = trim((string) ($input['project_name'] ?? ''));
            if ($projectName === '') {
                return ['success' => false, 'errors' => ['project_name' => 'Project name is required.']];
            }
            $data['project_name']        = $projectName;
            $data['project_description'] = trim((string) ($input['project_description'] ?? '')) ?: null;
        }

        if ($contractType === 'product') {
            $lines = $input['lines'] ?? [];
            if (! is_array($lines)) {
                $lines = [];
            }
            $hasLine = false;
            $subtotal = 0.0;
            foreach ($lines as $line) {
                $name = trim((string) ($line['item_name'] ?? ''));
                if ($name === '') {
                    continue;
                }
                $hasLine = true;
                $subtotal += round(((float) ($line['quantity'] ?? 1)) * ((float) ($line['unit_price'] ?? 0)), 2);
            }
            if (! $hasLine) {
                return ['success' => false, 'errors' => ['lines' => 'Add at least one product line.']];
            }
            if (! isset($input['value']) || $input['value'] === '') {
                $data['value'] = round($subtotal, 2);
            }
        }

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

    protected function syncChildren(int $contractId, string $type, array $input): void
    {
        if ($type === 'product') {
            $result = $this->lines->replaceForContract($contractId, $input['lines'] ?? []);
            if (! isset($input['value']) || $input['value'] === '') {
                $this->contracts->update($contractId, ['value' => $result['subtotal']]);
            }
            $this->milestones->where('contract_id', $contractId)->delete();
            $this->assignments->where('contract_id', $contractId)->delete();
            return;
        }

        if ($type === 'project') {
            $this->milestones->replaceForContract($contractId, $input['milestones'] ?? []);
            $this->lines->where('contract_id', $contractId)->delete();
            $this->assignments->where('contract_id', $contractId)->delete();
            return;
        }

        if ($type === 'service') {
            $this->assignments->replaceForContract($contractId, $this->normalizeEmployees($input['employees'] ?? []));
            $this->lines->where('contract_id', $contractId)->delete();
            $this->milestones->where('contract_id', $contractId)->delete();
        }
    }

    /**
     * @param array<int, mixed> $employees
     * @return list<array{employee_id:int, role_on_contract?:string, hours_allocated?:mixed}>
     */
    protected function normalizeEmployees(array $employees): array
    {
        $out = [];
        foreach ($employees as $row) {
            if (! is_array($row)) {
                // checkbox-style: employees[] = id
                $id = (int) $row;
                if ($id > 0) {
                    $out[] = ['employee_id' => $id];
                }
                continue;
            }
            $id = (int) ($row['employee_id'] ?? 0);
            if ($id <= 0) {
                continue;
            }
            if (! $this->employees->find($id)) {
                continue;
            }
            $out[] = $row;
        }

        return $out;
    }
}
