<?php
namespace App\Controllers;

use App\Core\Config;
use App\Core\Controller;
use App\Core\Database;
use App\Core\Mailer;
use App\Core\Notifier;
use PDO;

class PublicMobileApiController extends Controller
{
    private PDO $pdo;

    public function __construct()
    {
        $this->pdo = Database::pdo();
        $this->ensureTables();
    }

    private function ensureTables(): void
    {
        $sql = [
            "CREATE TABLE IF NOT EXISTS public_mobile_clients (
                id INT AUTO_INCREMENT PRIMARY KEY,
                full_name VARCHAR(255) NOT NULL,
                email VARCHAR(190) NULL,
                phone VARCHAR(60) NOT NULL,
                city VARCHAR(120) NULL,
                district VARCHAR(160) NULL,
                home_lat DECIMAL(10,7) NULL,
                home_lng DECIMAL(10,7) NULL,
                subscription_number VARCHAR(120) NOT NULL,
                password_hash VARCHAR(255) NULL,
                status ENUM('actif','suspendu') NOT NULL DEFAULT 'actif',
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uq_public_mobile_subscription (subscription_number),
                INDEX idx_public_mobile_phone (phone),
                INDEX idx_public_mobile_email (email)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
            "CREATE TABLE IF NOT EXISTS public_mobile_tokens (
                id INT AUTO_INCREMENT PRIMARY KEY,
                public_client_id INT NOT NULL,
                token_hash CHAR(64) NOT NULL,
                device_name VARCHAR(120) NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                last_used_at DATETIME NULL,
                expires_at DATETIME NULL,
                revoked_at DATETIME NULL,
                UNIQUE KEY uq_public_mobile_token_hash (token_hash),
                INDEX idx_public_mobile_token_client (public_client_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
            "CREATE TABLE IF NOT EXISTS public_mobile_incidents (
                id INT AUTO_INCREMENT PRIMARY KEY,
                public_client_id INT NOT NULL,
                ticket_id INT NOT NULL,
                subject VARCHAR(200) NULL,
                description TEXT NULL,
                status ENUM('soumis','prise_en_charge','assigne_technicien','en_cours','resolu','ferme') NOT NULL DEFAULT 'soumis',
                operator_notes TEXT NULL,
                submitted_lat DECIMAL(10,7) NULL,
                submitted_lng DECIMAL(10,7) NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_public_mobile_incidents_client (public_client_id),
                INDEX idx_public_mobile_incidents_ticket (ticket_id),
                INDEX idx_public_mobile_incidents_status (status)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
            "CREATE TABLE IF NOT EXISTS public_mobile_incident_photos (
                id INT AUTO_INCREMENT PRIMARY KEY,
                incident_id INT NOT NULL,
                photo_path VARCHAR(350) NOT NULL,
                comment TEXT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_public_mobile_incident_photos_incident (incident_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
            "CREATE TABLE IF NOT EXISTS public_mobile_incident_followups (
                id INT AUTO_INCREMENT PRIMARY KEY,
                incident_id INT NOT NULL,
                public_client_id INT NOT NULL,
                message TEXT NULL,
                channel VARCHAR(40) NOT NULL DEFAULT 'client_app',
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_public_mobile_followups_incident (incident_id),
                INDEX idx_public_mobile_followups_client (public_client_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
        ];

        foreach ($sql as $statement) {
            $this->pdo->exec($statement);
        }

        Notifier::ensurePublicClientNotificationsTable($this->pdo);

        foreach ([
            "ALTER TABLE public_mobile_clients ADD COLUMN email VARCHAR(190) NULL AFTER full_name",
            "ALTER TABLE public_mobile_clients ADD COLUMN password_hash VARCHAR(255) NULL AFTER subscription_number",
            "ALTER TABLE public_mobile_clients ADD COLUMN reset_code_hash VARCHAR(255) NULL AFTER password_hash",
            "ALTER TABLE public_mobile_clients ADD COLUMN reset_code_expires_at DATETIME NULL AFTER reset_code_hash",
            "ALTER TABLE public_mobile_clients ADD INDEX idx_public_mobile_email (email)",
            "ALTER TABLE maintenance_ftth_tickets ADD COLUMN public_client_id INT NULL AFTER client_email",
            "ALTER TABLE maintenance_ftth_tickets ADD COLUMN source_channel VARCHAR(40) NULL AFTER company_name",
            "ALTER TABLE maintenance_ftth_tickets ADD COLUMN last_public_incident_at DATETIME NULL AFTER source_channel",
            "ALTER TABLE public_mobile_incidents ADD COLUMN last_customer_followup_at DATETIME NULL AFTER updated_at"
        ] as $alterSql) {
            try {
                $this->pdo->exec($alterSql);
            } catch (\Throwable $e) {
            }
        }
    }

    private function json(array $data, int $status = 200): void
    {
        http_response_code($status);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode($data, JSON_UNESCAPED_UNICODE);
    }

    private function getBearerToken(): ?string
    {
        $hdr = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '';
        if (!$hdr) {
            return null;
        }
        if (preg_match('/^Bearer\s+(.+)$/i', trim($hdr), $m)) {
            return trim($m[1]);
        }
        return null;
    }

    private function createToken(int $clientId, string $deviceName = 'public-mobile', int $days = 180): string
    {
        $raw = bin2hex(random_bytes(32));
        $hash = hash('sha256', $raw);
        $expires = (new \DateTimeImmutable())->add(new \DateInterval('P' . $days . 'D'))->format('Y-m-d H:i:s');
        $st = $this->pdo->prepare('INSERT INTO public_mobile_tokens (public_client_id, token_hash, device_name, created_at, expires_at) VALUES (?,?,?,NOW(),?)');
        $st->execute([$clientId, $hash, $deviceName, $expires]);
        return $raw;
    }

    private function findClientFromToken(): ?array
    {
        $token = $this->getBearerToken();
        if (!$token) {
            return null;
        }

        $hash = hash('sha256', $token);
        $st = $this->pdo->prepare('SELECT public_client_id FROM public_mobile_tokens WHERE token_hash=? AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > NOW()) LIMIT 1');
        $st->execute([$hash]);
        $clientId = (int)($st->fetchColumn() ?: 0);
        if ($clientId <= 0) {
            return null;
        }

        $this->pdo->prepare('UPDATE public_mobile_tokens SET last_used_at = NOW() WHERE token_hash=?')->execute([$hash]);
        $clientSt = $this->pdo->prepare('SELECT * FROM public_mobile_clients WHERE id=? AND status=\'actif\' LIMIT 1');
        $clientSt->execute([$clientId]);
        $client = $clientSt->fetch(PDO::FETCH_ASSOC);
        return $client ?: null;
    }

    private function normalizeNullableString($value): ?string
    {
        $value = trim((string)$value);
        return $value !== '' ? $value : null;
    }

    private function normalizeEmail($value): ?string
    {
        $value = trim(strtolower((string)$value));
        if ($value === '' || !filter_var($value, FILTER_VALIDATE_EMAIL)) {
            return null;
        }
        return $value;
    }

    private function normalizePhone($value): string
    {
        $phone = trim((string)$value);
        if ($phone === '') {
            return '';
        }

        $phone = preg_replace('/[^0-9+]/', '', $phone) ?? '';
        if ($phone === '') {
            return '';
        }

        if (str_starts_with($phone, '00')) {
            $phone = '+' . substr($phone, 2);
        }

        if (str_starts_with($phone, '+')) {
            return '+' . preg_replace('/\D/', '', substr($phone, 1));
        }

        $digits = preg_replace('/\D/', '', $phone) ?? '';
        if ($digits === '') {
            return '';
        }

        if (strlen($digits) === 10 && str_starts_with($digits, '0')) {
            return '+225' . $digits;
        }

        if (strlen($digits) === 13 && str_starts_with($digits, '225')) {
            return '+' . $digits;
        }

        return $digits;
    }

    private function phoneCandidates(string $phone): array
    {
        $normalized = $this->normalizePhone($phone);
        if ($normalized === '') {
            return [];
        }

        $digits = preg_replace('/\D/', '', $normalized) ?? '';
        $candidates = [$normalized, $digits];

        if (strlen($digits) >= 8) {
            $candidates[] = substr($digits, -8);
        }

        if (strlen($digits) >= 10) {
            $localTenDigits = substr($digits, -10);
            $candidates[] = $localTenDigits;

            if (!str_starts_with($localTenDigits, '0') && strlen($localTenDigits) === 10) {
                $candidates[] = '0' . substr($localTenDigits, -9);
            }
        }

        if (str_starts_with($digits, '225') && strlen($digits) > 3) {
            $candidates[] = substr($digits, 3);
        }

        if (strlen($digits) === 10) {
            $candidates[] = '+225' . $digits;
            $candidates[] = '225' . $digits;
        }

        if (strlen($digits) === 8) {
            $candidates[] = '0' . $digits;
            $candidates[] = '+2250' . $digits;
            $candidates[] = '2250' . $digits;
        }

        return array_values(array_unique(array_filter($candidates, static fn ($value) => $value !== '')));
    }

    private function phoneMatches(string $storedPhone, string $inputPhone): bool
    {
        $storedCandidates = $this->phoneCandidates($storedPhone);
        $inputCandidates = $this->phoneCandidates($inputPhone);

        if (empty($storedCandidates) || empty($inputCandidates)) {
            return false;
        }

        return !empty(array_intersect($storedCandidates, $inputCandidates));
    }

    private function findClientByLogin(string $login): ?array
    {
        $email = $this->normalizeEmail($login);
        if ($email !== null) {
            $st = $this->pdo->prepare('SELECT * FROM public_mobile_clients WHERE email=? AND status=\'actif\' LIMIT 1');
            $st->execute([$email]);
            $client = $st->fetch(PDO::FETCH_ASSOC);
            if ($client) {
                return $client;
            }
        }

        $phoneCandidates = $this->phoneCandidates($login);
        if (empty($phoneCandidates)) {
            return null;
        }

        $st = $this->pdo->query("SELECT * FROM public_mobile_clients WHERE status='actif' AND phone IS NOT NULL AND phone <> ''");
        $clients = $st ? ($st->fetchAll(PDO::FETCH_ASSOC) ?: []) : [];

        foreach ($clients as $client) {
            if ($this->phoneMatches((string)($client['phone'] ?? ''), $login)) {
                return $client;
            }
        }

        return null;
    }

    private function verifyPassword(string $hash, string $password): bool
    {
        if ($hash === '' || $password === '') {
            return false;
        }

        if (str_starts_with($hash, '$2y$') || str_starts_with($hash, '$2a$') || str_starts_with($hash, '$argon2')) {
            return password_verify($password, $hash);
        }

        return hash_equals($hash, $password);
    }

    private function generateResetCode(): string
    {
        return (string)random_int(100000, 999999);
    }

    private function storeResetCode(int $clientId, string $code): void
    {
        $hash = password_hash($code, PASSWORD_DEFAULT);
        $expiresAt = (new \DateTimeImmutable('+30 minutes'))->format('Y-m-d H:i:s');
        $st = $this->pdo->prepare('UPDATE public_mobile_clients SET reset_code_hash=?, reset_code_expires_at=? WHERE id=?');
        $st->execute([$hash, $expiresAt, $clientId]);
    }

    private function clearResetCode(int $clientId): void
    {
        $st = $this->pdo->prepare('UPDATE public_mobile_clients SET reset_code_hash=NULL, reset_code_expires_at=NULL WHERE id=?');
        $st->execute([$clientId]);
    }

    private function sendResetCodeEmail(array $client, string $code): bool
    {
        $email = trim((string)($client['email'] ?? ''));
        if ($email === '') {
            return false;
        }

        $appName = (string)(Config::get('app_name', 'e-Intervention') ?: 'e-Intervention');
        $subject = '[' . $appName . '] Réinitialisation du mot de passe';
        $fullName = htmlspecialchars((string)($client['full_name'] ?? 'Client'), ENT_QUOTES, 'UTF-8');
        $safeCode = htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
        $html = '<p>Bonjour ' . $fullName . ',</p>'
            . '<p>Vous avez demandé la réinitialisation du mot de passe de votre espace client e-Intervention.</p>'
            . '<p><strong>Code de réinitialisation :</strong> <span style="font-size:22px;letter-spacing:4px;">' . $safeCode . '</span></p>'
            . '<p>Ce code est valable pendant 30 minutes.</p>'
            . '<p>Si vous n\'êtes pas à l\'origine de cette demande, vous pouvez ignorer cet email.</p>';

        return Mailer::send($email, (string)($client['full_name'] ?? 'Client'), $subject, $html, $this->pdo);
    }

    private function normalizeCoordinate($value): ?float
    {
        if ($value === null || $value === '') {
            return null;
        }
        $normalized = str_replace(',', '.', trim((string)$value));
        return is_numeric($normalized) ? (float)$normalized : null;
    }

    private function buildClientAddress(array $client): ?string
    {
        $parts = array_filter([
            trim((string)($client['district'] ?? '')),
            trim((string)($client['city'] ?? '')),
        ]);
        return !empty($parts) ? implode(', ', $parts) : null;
    }

    private function generateTicketRef(): string
    {
        return 'EINT-' . date('Ymd-His') . '-' . random_int(100, 999);
    }

    private function findOrCreateFtthTicket(array $client, ?float $lat, ?float $lng, ?string $comment): int
    {
        $subscription = trim((string)($client['subscription_number'] ?? ''));
        $address = $this->buildClientAddress($client);

        $st = $this->pdo->prepare(
            "SELECT * FROM maintenance_ftth_tickets WHERE (client_code = ? OR fixed_line = ?) ORDER BY FIELD(status, 'assigné', 'en_cours', 'attente_planification', 'nouveau', 'traité', 'validé', 'clôturé'), updated_at DESC LIMIT 1"
        );
        $st->execute([$subscription, $subscription]);
        $ticket = $st->fetch(PDO::FETCH_ASSOC);

        if ($ticket) {
            $updateFields = [
                'public_client_id' => (int)$client['id'],
                'source_channel' => 'e-intervention',
                'last_public_incident_at' => date('Y-m-d H:i:s'),
            ];

            if (empty($ticket['client_lat']) && $lat !== null) {
                $updateFields['client_lat'] = $lat;
            }
            if (empty($ticket['client_lng']) && $lng !== null) {
                $updateFields['client_lng'] = $lng;
            }
            if (empty($ticket['client_address']) && $address !== null) {
                $updateFields['client_address'] = $address;
            }
            if (empty($ticket['client_phone']) && !empty($client['phone'])) {
                $updateFields['client_phone'] = (string)$client['phone'];
            }

            $assignments = [];
            $params = [];
            foreach ($updateFields as $field => $value) {
                $assignments[] = $field . ' = ?';
                $params[] = $value;
            }
            $params[] = (int)$ticket['id'];
            $this->pdo->prepare('UPDATE maintenance_ftth_tickets SET ' . implode(', ', $assignments) . ' WHERE id=?')->execute($params);
            return (int)$ticket['id'];
        }

        $insert = $this->pdo->prepare(
            'INSERT INTO maintenance_ftth_tickets (
                ref_code, fixed_line, client_name, client_code, client_phone, client_address,
                client_lat, client_lng, company_name, nature_intervention, description,
                priority, status, public_client_id, source_channel, last_public_incident_at, imported_at
            ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())'
        );
        $insert->execute([
            $this->generateTicketRef(),
            $subscription,
            (string)$client['full_name'],
            $subscription,
            (string)$client['phone'],
            $address,
            $lat,
            $lng,
            'Interne ITC',
            'Déclaration client via e-Intervention',
            $comment ?: 'Déclaration créée depuis l\'application client.',
            'Moyenne',
            'nouveau',
            (int)$client['id'],
            'e-intervention',
            date('Y-m-d H:i:s'),
        ]);

        return (int)$this->pdo->lastInsertId();
    }

    private function saveIncidentPhotos(int $incidentId, int $clientId): void
    {
        if (empty($_FILES['photos'])) {
            return;
        }

        $names = (array)($_FILES['photos']['name'] ?? []);
        $tmpNames = (array)($_FILES['photos']['tmp_name'] ?? []);
        $errors = (array)($_FILES['photos']['error'] ?? []);
        $comments = array_values((array)($_POST['photo_comments'] ?? []));

        $baseDir = Config::storagePath('uploads/e-intervention/' . $clientId . '/' . $incidentId);
        if (!is_dir($baseDir)) {
            @mkdir($baseDir, 0777, true);
        }

        $insert = $this->pdo->prepare('INSERT INTO public_mobile_incident_photos (incident_id, photo_path, comment, created_at) VALUES (?,?,?,NOW())');

        foreach ($tmpNames as $index => $tmpName) {
            if (($errors[$index] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || !is_uploaded_file($tmpName)) {
                continue;
            }

            $ext = strtolower(pathinfo((string)($names[$index] ?? ''), PATHINFO_EXTENSION));
            if (!in_array($ext, ['jpg', 'jpeg', 'png', 'webp'], true)) {
                $ext = 'jpg';
            }

            $fileName = 'photo-' . str_pad((string)($index + 1), 2, '0', STR_PAD_LEFT) . '-' . bin2hex(random_bytes(4)) . '.' . $ext;
            $destination = $baseDir . DIRECTORY_SEPARATOR . $fileName;
            if (!@move_uploaded_file($tmpName, $destination)) {
                continue;
            }

            $relativePath = 'uploads/e-intervention/' . $clientId . '/' . $incidentId . '/' . $fileName;
            $insert->execute([
                $incidentId,
                $relativePath,
                trim((string)($comments[$index] ?? '')),
            ]);
        }
    }

    private function notifyOperatorByEmail(array $client, int $ticketId, int $incidentId, ?string $comment): void
    {
        $toEmail = trim((string)Config::get('operator_service_email', ''));
        if ($toEmail === '') {
            return;
        }

        $subject = '[e-Intervention] Nouvelle déclaration client';
        $detailUrl = rtrim(Config::baseUrl(), '/') . '/maintenance-ftth/detail?id=' . $ticketId;
        $html = '<p>Une nouvelle déclaration client a été soumise depuis l\'application e-Intervention.</p>'
            . '<ul>'
            . '<li><strong>Client :</strong> ' . htmlspecialchars((string)$client['full_name']) . '</li>'
            . '<li><strong>Contact :</strong> ' . htmlspecialchars((string)$client['phone']) . '</li>'
            . '<li><strong>Ville / Quartier :</strong> ' . htmlspecialchars(trim(((string)($client['city'] ?? '')) . ' / ' . ((string)($client['district'] ?? '')))) . '</li>'
            . '<li><strong>N° Abonnement :</strong> ' . htmlspecialchars((string)$client['subscription_number']) . '</li>'
            . '<li><strong>Ticket FTTH :</strong> #' . $ticketId . '</li>'
            . '<li><strong>Dossier public :</strong> #' . $incidentId . '</li>'
            . '</ul>'
            . '<p><strong>Commentaire client :</strong><br>' . nl2br(htmlspecialchars((string)$comment)) . '</p>'
            . '<p><a href="' . htmlspecialchars($detailUrl) . '">Ouvrir la maintenance FTTH B2B</a></p>';

        Mailer::send($toEmail, 'Service client opérateur', $subject, $html, $this->pdo);
    }

    private function notifyOperatorFollowupByEmail(array $client, array $incident, ?string $message): void
    {
        $toEmail = trim((string)Config::get('operator_service_email', ''));
        if ($toEmail === '') {
            return;
        }

        $ticketId = (int)($incident['ticket_id'] ?? 0);
        $incidentId = (int)($incident['id'] ?? 0);
        $detailUrl = $ticketId > 0 ? rtrim(Config::baseUrl(), '/') . '/maintenance-ftth/detail?id=' . $ticketId : '';
        $subject = '[e-Intervention] Relance client sur demande en cours';
        $html = '<p>Un client a demandé une relance sur sa déclaration e-Intervention.</p>'
            . '<ul>'
            . '<li><strong>Client :</strong> ' . htmlspecialchars((string)($client['full_name'] ?? 'Client'), ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>Contact :</strong> ' . htmlspecialchars((string)($client['phone'] ?? ''), ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>Email :</strong> ' . htmlspecialchars((string)($client['email'] ?? '—'), ENT_QUOTES, 'UTF-8') . '</li>'
            . '<li><strong>Ticket FTTH :</strong> #' . $ticketId . '</li>'
            . '<li><strong>Dossier public :</strong> #' . $incidentId . '</li>'
            . '<li><strong>Statut actuel :</strong> ' . htmlspecialchars((string)($incident['status'] ?? 'soumis'), ENT_QUOTES, 'UTF-8') . '</li>'
            . '</ul>';

        if ($message !== null && $message !== '') {
            $html .= '<p><strong>Message client :</strong><br>' . nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8')) . '</p>';
        }

        if ($detailUrl !== '') {
            $html .= '<p><a href="' . htmlspecialchars($detailUrl, ENT_QUOTES, 'UTF-8') . '">Ouvrir la maintenance FTTH B2B</a></p>';
        }

        Mailer::send($toEmail, 'Service client opérateur', $subject, $html, $this->pdo);
    }

    private function getIncidentFollowups(int $incidentId, int $clientId): array
    {
        $st = $this->pdo->prepare('SELECT id, incident_id, public_client_id, message, channel, created_at FROM public_mobile_incident_followups WHERE incident_id=? AND public_client_id=? ORDER BY created_at DESC, id DESC');
        $st->execute([$incidentId, $clientId]);
        return $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
    }

    public function register(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
            $this->json(['ok' => false, 'error' => 'method_not_allowed'], 405);
            return;
        }

        $fullName = trim((string)($_POST['full_name'] ?? ''));
        $email = $this->normalizeEmail($_POST['email'] ?? '');
        $phone = $this->normalizePhone($_POST['phone'] ?? '');
        $city = $this->normalizeNullableString($_POST['city'] ?? '');
        $district = $this->normalizeNullableString($_POST['district'] ?? '');
        $subscriptionNumber = trim((string)($_POST['subscription_number'] ?? ''));
        $password = (string)($_POST['password'] ?? '');
        $homeLat = $this->normalizeCoordinate($_POST['home_lat'] ?? null);
        $homeLng = $this->normalizeCoordinate($_POST['home_lng'] ?? null);

        if ($fullName === '' || $phone === '' || $subscriptionNumber === '' || $password === '') {
            $this->json(['ok' => false, 'error' => 'missing_required_fields'], 400);
            return;
        }

        if ($email === null && trim((string)($_POST['email'] ?? '')) !== '') {
            $this->json(['ok' => false, 'error' => 'invalid_email'], 400);
            return;
        }

        if (mb_strlen($password) < 6) {
            $this->json(['ok' => false, 'error' => 'weak_password'], 400);
            return;
        }

        if ($email !== null) {
            $emailCheck = $this->pdo->prepare('SELECT id FROM public_mobile_clients WHERE email=? AND subscription_number<>? LIMIT 1');
            $emailCheck->execute([$email, $subscriptionNumber]);
            if ((int)($emailCheck->fetchColumn() ?: 0) > 0) {
                $this->json(['ok' => false, 'error' => 'email_already_used'], 409);
                return;
            }
        }

        $passwordHash = password_hash($password, PASSWORD_DEFAULT);

        $existingSt = $this->pdo->prepare('SELECT id FROM public_mobile_clients WHERE subscription_number=? LIMIT 1');
        $existingSt->execute([$subscriptionNumber]);
        $existingId = (int)($existingSt->fetchColumn() ?: 0);

        if ($existingId > 0) {
            $update = $this->pdo->prepare('UPDATE public_mobile_clients SET full_name=?, email=?, phone=?, city=?, district=?, home_lat=?, home_lng=?, password_hash=?, status=\'actif\' WHERE id=?');
            $update->execute([$fullName, $email, $phone, $city, $district, $homeLat, $homeLng, $passwordHash, $existingId]);
            $clientId = $existingId;
        } else {
            $insert = $this->pdo->prepare('INSERT INTO public_mobile_clients (full_name, email, phone, city, district, home_lat, home_lng, subscription_number, password_hash, status, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,\'actif\',NOW(),NOW())');
            $insert->execute([$fullName, $email, $phone, $city, $district, $homeLat, $homeLng, $subscriptionNumber, $passwordHash]);
            $clientId = (int)$this->pdo->lastInsertId();
        }

        $clientSt = $this->pdo->prepare('SELECT * FROM public_mobile_clients WHERE id=? LIMIT 1');
        $clientSt->execute([$clientId]);
        $client = $clientSt->fetch(PDO::FETCH_ASSOC);

        $token = $this->createToken($clientId, 'e-intervention');
        Notifier::notifyPublicClients($this->pdo, [$clientId], '[e-Intervention] Bienvenue', 'Votre espace client est prêt. Vous pouvez désormais déclarer un incident et suivre sa prise en charge.', '#/dashboard');

        $this->json(['ok' => true, 'token' => $token, 'client' => $client]);
    }

    public function login(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
            $this->json(['ok' => false, 'error' => 'method_not_allowed'], 405);
            return;
        }

        $login = trim((string)($_POST['login'] ?? $_POST['phone'] ?? $_POST['email'] ?? ''));
        $password = (string)($_POST['password'] ?? '');
        if ($login === '' || $password === '') {
            $this->json(['ok' => false, 'error' => 'missing_credentials'], 400);
            return;
        }

        $client = $this->findClientByLogin($login);

        if (!$client || !$this->verifyPassword((string)($client['password_hash'] ?? ''), $password)) {
            $this->json(['ok' => false, 'error' => 'invalid_credentials'], 401);
            return;
        }

        $token = $this->createToken((int)$client['id'], 'e-intervention');
        $this->json(['ok' => true, 'token' => $token, 'client' => $client]);
    }

    public function forgotPassword(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
            $this->json(['ok' => false, 'error' => 'method_not_allowed'], 405);
            return;
        }

        $login = trim((string)($_POST['login'] ?? $_POST['phone'] ?? $_POST['email'] ?? ''));
        if ($login === '') {
            $this->json(['ok' => false, 'error' => 'missing_login'], 400);
            return;
        }

        $client = $this->findClientByLogin($login);
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'account_not_found'], 404);
            return;
        }

        $email = trim((string)($client['email'] ?? ''));
        if ($email === '') {
            $this->json(['ok' => false, 'error' => 'missing_reset_email'], 400);
            return;
        }

        $code = $this->generateResetCode();
        $this->storeResetCode((int)$client['id'], $code);

        if (!$this->sendResetCodeEmail($client, $code)) {
            $this->clearResetCode((int)$client['id']);
            $this->json(['ok' => false, 'error' => 'email_send_failed'], 500);
            return;
        }

        $maskedEmail = preg_replace('/(^.).+(@.+$)/', '$1***$2', $email) ?: $email;
        $this->json([
            'ok' => true,
            'message' => 'Un code de réinitialisation a été envoyé à votre adresse email.',
            'masked_email' => $maskedEmail,
        ]);
    }

    public function resetPassword(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
            $this->json(['ok' => false, 'error' => 'method_not_allowed'], 405);
            return;
        }

        $login = trim((string)($_POST['login'] ?? $_POST['phone'] ?? $_POST['email'] ?? ''));
        $code = trim((string)($_POST['code'] ?? ''));
        $password = (string)($_POST['password'] ?? '');

        if ($login === '' || $code === '' || $password === '') {
            $this->json(['ok' => false, 'error' => 'missing_required_fields'], 400);
            return;
        }

        if (mb_strlen($password) < 6) {
            $this->json(['ok' => false, 'error' => 'weak_password'], 400);
            return;
        }

        $client = $this->findClientByLogin($login);
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'account_not_found'], 404);
            return;
        }

        $expiresAt = trim((string)($client['reset_code_expires_at'] ?? ''));
        $hash = (string)($client['reset_code_hash'] ?? '');
        if ($hash === '' || $expiresAt === '') {
            $this->json(['ok' => false, 'error' => 'reset_not_requested'], 400);
            return;
        }

        if (strtotime($expiresAt) < time()) {
            $this->clearResetCode((int)$client['id']);
            $this->json(['ok' => false, 'error' => 'reset_code_expired'], 400);
            return;
        }

        if (!password_verify($code, $hash)) {
            $this->json(['ok' => false, 'error' => 'invalid_reset_code'], 400);
            return;
        }

        $passwordHash = password_hash($password, PASSWORD_DEFAULT);
        $st = $this->pdo->prepare('UPDATE public_mobile_clients SET password_hash=?, reset_code_hash=NULL, reset_code_expires_at=NULL WHERE id=?');
        $st->execute([$passwordHash, (int)$client['id']]);

        $this->json(['ok' => true, 'message' => 'Votre mot de passe a été réinitialisé.']);
    }

    public function me(): void
    {
        $client = $this->findClientFromToken();
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'unauthorized'], 401);
            return;
        }

        $this->json(['ok' => true, 'client' => $client]);
    }

    public function createIncident(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
            $this->json(['ok' => false, 'error' => 'method_not_allowed'], 405);
            return;
        }

        $client = $this->findClientFromToken();
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'unauthorized'], 401);
            return;
        }

        $subject = trim((string)($_POST['subject'] ?? 'Incident client'));
        $description = trim((string)($_POST['description'] ?? ''));
        $submittedLat = $this->normalizeCoordinate($_POST['submitted_lat'] ?? null) ?? $this->normalizeCoordinate($client['home_lat'] ?? null);
        $submittedLng = $this->normalizeCoordinate($_POST['submitted_lng'] ?? null) ?? $this->normalizeCoordinate($client['home_lng'] ?? null);

        if ($description === '') {
            $this->json(['ok' => false, 'error' => 'missing_description'], 400);
            return;
        }

        $ticketId = $this->findOrCreateFtthTicket($client, $submittedLat, $submittedLng, $description);

        $incidentSt = $this->pdo->prepare('INSERT INTO public_mobile_incidents (public_client_id, ticket_id, subject, description, status, submitted_lat, submitted_lng, created_at, updated_at) VALUES (?,?,?,?,\'soumis\',?,?,NOW(),NOW())');
        $incidentSt->execute([
            (int)$client['id'],
            $ticketId,
            $subject,
            $description,
            $submittedLat,
            $submittedLng,
        ]);
        $incidentId = (int)$this->pdo->lastInsertId();

        $this->saveIncidentPhotos($incidentId, (int)$client['id']);

        Notifier::notifyPublicClients($this->pdo, [(int)$client['id']], '[e-Intervention] Demande reçue', 'Votre déclaration a été enregistrée et transmise au service client pour prise en charge.', '#/history');
        $this->notifyOperatorByEmail($client, $ticketId, $incidentId, $description);

        $this->json([
            'ok' => true,
            'incident_id' => $incidentId,
            'ticket_id' => $ticketId,
            'status' => 'soumis',
        ]);
    }

    public function incidents(): void
    {
        $client = $this->findClientFromToken();
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'unauthorized'], 401);
            return;
        }

        $st = $this->pdo->prepare(
            "SELECT i.*, t.ref_code, t.client_address, t.nature_intervention,
                    (SELECT COUNT(*) FROM public_mobile_incident_photos p WHERE p.incident_id=i.id) AS photo_count
             FROM public_mobile_incidents i
             JOIN maintenance_ftth_tickets t ON t.id = i.ticket_id
             WHERE i.public_client_id=?
             ORDER BY i.created_at DESC"
        );
        $st->execute([(int)$client['id']]);
        $items = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];

        $this->json(['ok' => true, 'items' => $items]);
    }

    public function incidentDetail(): void
    {
        $client = $this->findClientFromToken();
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'unauthorized'], 401);
            return;
        }

        $incidentId = (int)($_GET['id'] ?? 0);
        if ($incidentId <= 0) {
            $this->json(['ok' => false, 'error' => 'invalid_incident'], 400);
            return;
        }

        $st = $this->pdo->prepare(
            "SELECT i.*, t.ref_code, t.client_address, t.nature_intervention,
                    (SELECT COUNT(*) FROM public_mobile_incident_photos p WHERE p.incident_id=i.id) AS photo_count
             FROM public_mobile_incidents i
             JOIN maintenance_ftth_tickets t ON t.id = i.ticket_id
             WHERE i.id=? AND i.public_client_id=?
             LIMIT 1"
        );
        $st->execute([$incidentId, (int)$client['id']]);
        $incident = $st->fetch(PDO::FETCH_ASSOC);

        if (!$incident) {
            $this->json(['ok' => false, 'error' => 'not_found'], 404);
            return;
        }

        $photoSt = $this->pdo->prepare('SELECT id, photo_path, comment, created_at FROM public_mobile_incident_photos WHERE incident_id=? ORDER BY created_at ASC');
        $photoSt->execute([$incidentId]);
        $photos = $photoSt->fetchAll(PDO::FETCH_ASSOC) ?: [];

        foreach ($photos as &$photo) {
            $cleanPath = ltrim((string)($photo['photo_path'] ?? ''), '/');
            $photo['photo_url'] = $cleanPath !== '' ? base_url('storage/' . $cleanPath) : null;
        }
        unset($photo);

        $followups = $this->getIncidentFollowups($incidentId, (int)$client['id']);

        $this->json([
            'ok' => true,
            'incident' => $incident,
            'photos' => $photos,
            'followups' => $followups,
            'support' => [
                'email' => trim((string)Config::get('operator_service_email', '')),
            ],
        ]);
    }

    public function remindIncident(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
            $this->json(['ok' => false, 'error' => 'method_not_allowed'], 405);
            return;
        }

        $client = $this->findClientFromToken();
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'unauthorized'], 401);
            return;
        }

        $incidentId = (int)($_POST['incident_id'] ?? 0);
        $message = trim((string)($_POST['message'] ?? ''));
        if ($incidentId <= 0) {
            $this->json(['ok' => false, 'error' => 'invalid_incident'], 400);
            return;
        }

        $st = $this->pdo->prepare('SELECT * FROM public_mobile_incidents WHERE id=? AND public_client_id=? LIMIT 1');
        $st->execute([$incidentId, (int)$client['id']]);
        $incident = $st->fetch(PDO::FETCH_ASSOC);
        if (!$incident) {
            $this->json(['ok' => false, 'error' => 'not_found'], 404);
            return;
        }

        if (in_array((string)($incident['status'] ?? ''), ['resolu', 'ferme'], true)) {
            $this->json(['ok' => false, 'error' => 'incident_closed'], 409);
            return;
        }

        $notePrefix = '[' . date('d/m/Y H:i') . '] Relance client';
        $note = $message !== '' ? $notePrefix . ' : ' . $message : $notePrefix . ' demandée depuis l\'application client.';
        $existingNotes = trim((string)($incident['operator_notes'] ?? ''));
        $mergedNotes = $existingNotes !== '' ? $existingNotes . PHP_EOL . $note : $note;

        $up = $this->pdo->prepare('UPDATE public_mobile_incidents SET operator_notes=?, last_customer_followup_at=NOW(), updated_at=NOW() WHERE id=?');
        $up->execute([$mergedNotes, $incidentId]);

        $followupInsert = $this->pdo->prepare('INSERT INTO public_mobile_incident_followups (incident_id, public_client_id, message, channel, created_at) VALUES (?,?,?,?,NOW())');
        $followupInsert->execute([
            $incidentId,
            (int)$client['id'],
            $message !== '' ? $message : 'Relance demandée depuis l\'application client.',
            'client_app',
        ]);

        $followupId = (int)$this->pdo->lastInsertId();

        $this->notifyOperatorFollowupByEmail($client, $incident, $message);
        Notifier::notifyPublicClients($this->pdo, [(int)$client['id']], '[e-Intervention] Relance enregistrée', 'Votre relance a bien été transmise au service client opérateur.', '#/incident?id=' . $incidentId);

        $this->json([
            'ok' => true,
            'message' => 'Votre relance a été transmise au support.',
            'followup' => [
                'id' => $followupId,
                'incident_id' => $incidentId,
                'public_client_id' => (int)$client['id'],
                'message' => $message !== '' ? $message : 'Relance demandée depuis l\'application client.',
                'channel' => 'client_app',
                'created_at' => date('Y-m-d H:i:s'),
            ],
        ]);
    }

    public function notifications(): void
    {
        $client = $this->findClientFromToken();
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'unauthorized'], 401);
            return;
        }

        $st = $this->pdo->prepare('SELECT id, title, body, url, is_read, created_at FROM public_mobile_notifications WHERE public_client_id=? ORDER BY created_at DESC LIMIT 100');
        $st->execute([(int)$client['id']]);
        $items = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
        $this->json(['ok' => true, 'notifications' => $items]);
    }

    public function notificationsUnreadCount(): void
    {
        $client = $this->findClientFromToken();
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'unauthorized'], 401);
            return;
        }

        $st = $this->pdo->prepare('SELECT COUNT(*) FROM public_mobile_notifications WHERE public_client_id=? AND is_read=0');
        $st->execute([(int)$client['id']]);
        $count = (int)($st->fetchColumn() ?: 0);
        $this->json(['ok' => true, 'unread' => $count]);
    }

    public function notificationsReadAll(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
            $this->json(['ok' => false, 'error' => 'method_not_allowed'], 405);
            return;
        }

        $client = $this->findClientFromToken();
        if (!$client) {
            $this->json(['ok' => false, 'error' => 'unauthorized'], 401);
            return;
        }

        $this->pdo->prepare('UPDATE public_mobile_notifications SET is_read=1 WHERE public_client_id=?')->execute([(int)$client['id']]);
        $this->json(['ok' => true]);
    }
}