<?php

namespace App\Controllers;

use App\Libraries\FolioService;
use App\Models\AuditLogModel;
use App\Models\ClientModel;
use App\Models\DeliveryNoteInspectionModel;
use App\Models\DeliveryNoteModel;
use App\Models\DeliveryNotePhotoModel;
use App\Models\DeliveryNoteTemplateModel;
use App\Models\InspectionModel;
use App\Models\SignatureModel;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\ResponseInterface;
use Mpdf\Mpdf;

class DeliveryNoteController extends BaseController
{
    public function index(): string
    {
        $notes = (new DeliveryNoteModel())
            ->select('delivery_notes.*, clients.nombre AS client_name')
            ->join('clients', 'clients.id = delivery_notes.client_id', 'left')
            ->orderBy('delivery_notes.id', 'DESC')
            ->findAll();

        return view('delivery_notes/index', [
            'title' => 'Actas de entrega',
            'notes' => $notes,
        ]);
    }

    public function create(): string
    {
        return view('delivery_notes/create', [
            'title' => 'Nueva acta de entrega',
            'clients' => (new ClientModel())->orderBy('nombre')->findAll(),
            'templates' => (new DeliveryNoteTemplateModel())->where('is_active', 1)->orderBy('code')->findAll(),
            'inspections' => (new InspectionModel())->where('status', 'signed')->orderBy('id', 'DESC')->findAll(),
        ]);
    }

    public function store(): RedirectResponse
    {
        $clientId = (int) $this->request->getPost('client_id');
        $templateId = (int) $this->request->getPost('template_id');
        $inspectionIds = array_map('intval', (array) $this->request->getPost('inspection_ids'));

        $template = (new DeliveryNoteTemplateModel())->find($templateId);
        if ($clientId === 0 || $template === null) {
            return redirect()->back()->withInput()->with('error', 'Cliente y plantilla son obligatorios.');
        }

        $title = trim((string) $this->request->getPost('title'));
        if ($title === '') {
            $title = $template['name'];
        }

        $data = [
            'folio' => (new FolioService())->next('delivery_note'),
            'template_id' => $templateId,
            'client_id' => $clientId,
            'title' => $title,
            'body_html' => $template['body_html'],
            'field_values_json' => '{}',
            'status' => 'draft',
            'verify_token' => FolioService::token(),
        ];

        $id = (new DeliveryNoteModel())->insert($data, true);
        $pivot = new DeliveryNoteInspectionModel();
        foreach (array_values(array_filter($inspectionIds)) as $orden => $inspectionId) {
            $pivot->insert([
                'delivery_note_id' => $id,
                'inspection_id' => $inspectionId,
                'orden' => $orden,
            ]);
        }

        (new AuditLogModel())->record('delivery_note', (int) $id, 'create', ['folio' => $data['folio']]);
        return redirect()->to(site_url('delivery-notes/' . $id))->with('success', 'Acta creada (folio ' . $data['folio'] . ').');
    }

    public function show(int $id): string
    {
        [$note, $template, $client, $inspections] = $this->loadNoteContext($id);
        $templateModel = new DeliveryNoteTemplateModel();
        $noteModel = new DeliveryNoteModel();
        $fields = $templateModel->fields($template);
        $values = $noteModel->values($note);

        return view('delivery_notes/show', [
            'title' => 'Acta ' . ($note['folio'] ?? '#' . $id),
            'note' => $note,
            'template' => $template,
            'client' => $client,
            'inspections' => $inspections,
            'fields' => $fields,
            'values' => $values,
            'signatures' => $this->signaturesFor($id, $fields),
            'photos' => (new DeliveryNotePhotoModel())->forDeliveryNote($id),
        ]);
    }

    public function update(int $id): RedirectResponse
    {
        [$note, $template] = $this->loadNoteContext($id);
        $fields = (new DeliveryNoteTemplateModel())->fields($template);
        $values = [];
        $signatureFields = [];

        foreach ($fields as $field) {
            $key = (string) ($field['key'] ?? '');
            if ($key === '') {
                continue;
            }
            if (($field['type'] ?? '') === 'signature') {
                $signatureFields[] = $key;
                continue;
            }
            $values[$key] = trim((string) $this->request->getPost('field_' . $key));
        }

        $status = in_array($this->request->getPost('status'), ['draft', 'signed', 'delivered'], true)
            ? (string) $this->request->getPost('status')
            : 'draft';

        $payload = [
            'title' => trim((string) $this->request->getPost('title')) ?: $note['title'],
            'field_values_json' => json_encode($values, JSON_UNESCAPED_UNICODE),
            'status' => $status,
        ];

        if ($status !== 'draft' && $this->allSignatureFieldsSigned($id, $signatureFields)) {
            $payload['signed_at'] = date('Y-m-d H:i:s');
        }

        (new DeliveryNoteModel())->update($id, $payload);
        (new AuditLogModel())->record('delivery_note', $id, 'update');

        return redirect()->to(site_url('delivery-notes/' . $id))->with('success', 'Acta actualizada.');
    }

    public function delete(int $id): RedirectResponse
    {
        (new DeliveryNoteModel())->delete($id);
        (new AuditLogModel())->record('delivery_note', $id, 'delete');
        return redirect()->to(site_url('delivery-notes'))->with('success', 'Acta eliminada.');
    }

    public function duplicate(int $id): RedirectResponse
    {
        [$note, $template] = $this->loadNoteContext($id);
        $fields = (new DeliveryNoteTemplateModel())->fields($template);
        $values = (new DeliveryNoteModel())->values($note);

        foreach ($fields as $field) {
            if (($field['type'] ?? '') === 'date' && isset($values[$field['key']])) {
                $values[$field['key']] = '';
            }
        }

        $newData = [
            'folio' => (new FolioService())->next('delivery_note'),
            'template_id' => (int) $note['template_id'],
            'client_id' => (int) $note['client_id'],
            'title' => $note['title'],
            'body_html' => $note['body_html'],
            'field_values_json' => json_encode($values, JSON_UNESCAPED_UNICODE),
            'status' => 'draft',
            'verify_token' => FolioService::token(),
            'signed_at' => null,
        ];

        $newId = (new DeliveryNoteModel())->insert($newData, true);

        $pivot = new DeliveryNoteInspectionModel();
        $links = $pivot->where('delivery_note_id', $id)->orderBy('orden')->findAll();
        foreach ($links as $link) {
            $pivot->insert([
                'delivery_note_id' => $newId,
                'inspection_id' => (int) $link['inspection_id'],
                'orden' => (int) $link['orden'],
            ]);
        }

        $this->copyPhotos($id, (int) $newId);

        (new AuditLogModel())->record('delivery_note', (int) $newId, 'duplicate', [
            'from_id' => $id,
            'from_folio' => $note['folio'] ?? null,
        ]);

        return redirect()
            ->to(site_url('delivery-notes/' . $newId))
            ->with('success', 'Acta duplicada con folio ' . $newData['folio'] . '.');
    }

    public function pdf(int $id): ResponseInterface
    {
        return $this->renderPdf($id);
    }

    public function renderPdf(int $id, bool $forceDownload = false): ResponseInterface
    {
        [$note, $template, $client, $inspections] = $this->loadNoteContext($id);
        $fields = (new DeliveryNoteTemplateModel())->fields($template);
        $values = (new DeliveryNoteModel())->values($note);
        $signatures = $this->signaturesFor($id, $fields);
        $photos = (new DeliveryNotePhotoModel())->forDeliveryNote($id);

        $html = view('pdf/delivery_note', [
            'note' => $note,
            'client' => $client,
            'template' => $template,
            'inspections' => $inspections,
            'fields' => $fields,
            'values' => $values,
            'signatures' => $signatures,
            'photos' => $photos,
            'renderedBody' => $this->renderBody($note['body_html'], $client, $values),
            'company' => company_profile(),
            'logoPath' => company_brand_asset_path('logo'),
            'verifyUrl' => site_url('verify/' . $note['verify_token']),
        ]);

        $mpdf = new Mpdf([
            'mode' => 'utf-8',
            'format' => 'Letter',
            'margin_left' => 12,
            'margin_right' => 12,
            'margin_top' => 12,
            'margin_bottom' => 14,
        ]);
        $mpdf->WriteHTML($html);

        $filename = ($note['folio'] ?? ('acta-' . $id)) . '.pdf';
        $blob = $mpdf->Output($filename, 'S');
        $response = $this->response ?? service('response');

        return $response
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', ($forceDownload ? 'attachment' : 'inline') . '; filename="' . $filename . '"')
            ->setBody($blob);
    }

    public function uploadPhoto(int $id): RedirectResponse
    {
        if ((new DeliveryNoteModel())->find($id) === null) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }

        $files = $this->request->getFiles();
        $uploads = $files['photos'] ?? [];
        if (! is_array($uploads)) {
            $uploads = [$uploads];
        }

        $section = $this->normalizePhotoSection((string) $this->request->getPost('section'));
        $caption = trim((string) $this->request->getPost('caption')) ?: null;
        $dir = WRITEPATH . 'uploads/delivery_notes/' . $id;
        if (! is_dir($dir)) {
            mkdir($dir, 0775, true);
        }

        $model = new DeliveryNotePhotoModel();
        $maxOrder = (int) ($model->where('delivery_note_id', $id)->selectMax('orden')->first()['orden'] ?? 0);
        $saved = 0;

        foreach ($uploads as $file) {
            if ($file === null || ! $file->isValid() || ! str_starts_with((string) $file->getMimeType(), 'image/')) {
                continue;
            }

            $mime = $file->getMimeType();
            $size = $file->getSize();
            $name = $file->getRandomName();
            $file->move($dir, $name);
            $model->insert([
                'delivery_note_id' => $id,
                'section' => $section,
                'caption' => $caption,
                'file_path' => 'uploads/delivery_notes/' . $id . '/' . $name,
                'mime' => $mime,
                'size' => $size ?: null,
                'orden' => ++$maxOrder,
                'created_at' => date('Y-m-d H:i:s'),
            ]);
            $saved++;
        }

        if ($saved === 0) {
            return redirect()->back()->with('error', 'Selecciona al menos una imagen válida.');
        }

        (new AuditLogModel())->record('delivery_note', $id, 'upload_photos', ['count' => $saved, 'section' => $section]);
        return redirect()->to(site_url('delivery-notes/' . $id))->with('success', $saved . ' imagen(es) agregada(s).');
    }

    public function deletePhoto(int $id, int $photoId): RedirectResponse
    {
        if ((new DeliveryNoteModel())->find($id) === null) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }

        $model = new DeliveryNotePhotoModel();
        $photo = $model->where('delivery_note_id', $id)->find($photoId);
        if ($photo !== null) {
            $path = WRITEPATH . $photo['file_path'];
            if (is_file($path)) {
                @unlink($path);
            }
            $model->delete($photoId);
            (new AuditLogModel())->record('delivery_note', $id, 'delete_photo', ['photo_id' => $photoId]);
        }

        return redirect()->to(site_url('delivery-notes/' . $id))->with('success', 'Imagen eliminada.');
    }

    public function photo(int $id, int $photoId): ResponseInterface
    {
        if ((new DeliveryNoteModel())->find($id) === null) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }

        $photo = (new DeliveryNotePhotoModel())->where('delivery_note_id', $id)->find($photoId);
        if ($photo === null) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }

        $path = WRITEPATH . $photo['file_path'];
        if (! is_file($path)) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }

        return $this->response
            ->setHeader('Content-Type', $photo['mime'] ?: mime_content_type($path))
            ->setBody(file_get_contents($path));
    }

    private function loadNoteContext(int $id): array
    {
        $note = (new DeliveryNoteModel())->find($id);
        if ($note === null) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
        }

        $template = (new DeliveryNoteTemplateModel())->find((int) $note['template_id']);
        $client = (new ClientModel())->find((int) $note['client_id']);
        $inspections = (new InspectionModel())
            ->select('inspections.*')
            ->join('delivery_note_inspections', 'delivery_note_inspections.inspection_id = inspections.id')
            ->where('delivery_note_inspections.delivery_note_id', $id)
            ->orderBy('delivery_note_inspections.orden')
            ->findAll();

        return [$note, $template, $client, $inspections];
    }

    private function signaturesFor(int $id, array $fields): array
    {
        $model = new SignatureModel();
        $signatures = [];
        foreach ($fields as $field) {
            if (($field['type'] ?? '') !== 'signature') {
                continue;
            }
            $key = (string) ($field['key'] ?? '');
            if ($key !== '') {
                $signatures[$key] = $model->findFor('delivery_note', $id, $key);
            }
        }

        return $signatures;
    }

    private function allSignatureFieldsSigned(int $id, array $keys): bool
    {
        if ($keys === []) {
            return true;
        }

        $model = new SignatureModel();
        foreach ($keys as $key) {
            if ($model->findFor('delivery_note', $id, $key) === null) {
                return false;
            }
        }

        return true;
    }

    private function renderBody(string $body, ?array $client, array $values): string
    {
        $replacements = ['cliente' => $client['nombre'] ?? ''];
        foreach ($values as $key => $value) {
            $replacements[$key] = nl2br(esc((string) $value));
        }

        return preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', static function (array $m) use ($replacements): string {
            $key = $m[1];
            return array_key_exists($key, $replacements) ? (string) $replacements[$key] : '';
        }, $body) ?? $body;
    }

    private function normalizePhotoSection(string $section): string
    {
        return match ($section) {
            'antes', 'durante', 'despues' => $section,
            default => 'general',
        };
    }

    private function copyPhotos(int $sourceId, int $targetId): void
    {
        $model = new DeliveryNotePhotoModel();
        $photos = $model->forDeliveryNote($sourceId);
        if ($photos === []) {
            return;
        }

        $targetDir = WRITEPATH . 'uploads/delivery_notes/' . $targetId;
        if (! is_dir($targetDir)) {
            mkdir($targetDir, 0775, true);
        }

        foreach ($photos as $photo) {
            $sourcePath = WRITEPATH . $photo['file_path'];
            if (! is_file($sourcePath)) {
                continue;
            }

            $extension = pathinfo($sourcePath, PATHINFO_EXTENSION);
            $filename = bin2hex(random_bytes(8)) . ($extension ? '.' . $extension : '');
            $targetRel = 'uploads/delivery_notes/' . $targetId . '/' . $filename;
            if (! @copy($sourcePath, WRITEPATH . $targetRel)) {
                continue;
            }

            $model->insert([
                'delivery_note_id' => $targetId,
                'section' => $photo['section'] ?? 'general',
                'caption' => $photo['caption'] ?? null,
                'file_path' => $targetRel,
                'mime' => $photo['mime'] ?? null,
                'size' => is_file(WRITEPATH . $targetRel) ? filesize(WRITEPATH . $targetRel) : ($photo['size'] ?? null),
                'orden' => (int) ($photo['orden'] ?? 0),
                'created_at' => date('Y-m-d H:i:s'),
            ]);
        }
    }
}
