<?php
/**
 * Geofences — circle-based zones (depot, customer site, no-go).
 *
 * MVP scope: circles only (center_lat + center_lng + radius_m). Polygons live
 * in geometry_json for Iter 3.3 when we add the Leaflet editor. Circle math
 * uses Haversine; server-side match runs in PHP (fine for < 200 fences).
 *
 * On position ingestion, GpsPositions::ingest() calls Geofences::matchPoint()
 * to detect enter/exit transitions vs the device's previous fence membership.
 */
class Geofences extends Model
{
    private function s($v): string { return trim((string)$v); }

    public function listPaginated(int $page, int $perPage, array $filters = []): array
    {
        $where  = ['g.deleted = 0', 'g.tenant_id = :tenant'];
        $params = [':tenant' => defined('DEFAULT_TENANT_ID') ? DEFAULT_TENANT_ID : 1];

        if (!empty($filters['q'])) {
            $where[] = '(g.name LIKE :q OR g.notes LIKE :q)';
            $params[':q'] = '%' . $filters['q'] . '%';
        }
        if (!empty($filters['kind']))   { $where[] = 'g.kind = :kd';   $params[':kd']  = $this->s($filters['kind']); }
        if (isset($filters['active']) && $filters['active'] !== '') {
            $where[] = 'g.active = :ac'; $params[':ac'] = (int)!!$filters['active'];
        }

        $whereSql = implode(' AND ', $where);
        $db = new DB();

        $countRs = $db->mySelect("SELECT COUNT(*) AS n FROM geofences g WHERE $whereSql", $params);
        $total = (int)($countRs[0]['n'] ?? 0);

        $page    = max(1, $page);
        $perPage = max(1, min(200, $perPage));
        $offset  = ($page - 1) * $perPage;

        $rows = $db->mySelect(
            "SELECT g.c0d, g.name, g.kind, g.center_lat, g.center_lng, g.radius_m,
                    g.color, g.active, g.trigger_alert, g.customer_id
               FROM geofences g
              WHERE $whereSql
           ORDER BY g.active DESC, g.name ASC
              LIMIT $perPage OFFSET $offset",
            $params
        ) ?: [];

        return ['rows' => $rows, 'total' => $total];
    }

    public function byId(int $id): array
    {
        $rs = (new DB())->mySelect(
            "SELECT g.*, TRIM(CONCAT(c.fname, ' ', c.lname)) AS customer_name
               FROM geofences g
          LEFT JOIN customers c ON c.c0d = g.customer_id
              WHERE g.c0d = :id AND g.deleted = 0
              LIMIT 1",
            [':id' => $id]
        );
        return $rs[0] ?? [];
    }

    public function activeAll(): array
    {
        return (new DB())->mySelect(
            "SELECT c0d, name, kind, center_lat, center_lng, radius_m, trigger_alert
               FROM geofences
              WHERE active = 1 AND deleted = 0 AND center_lat IS NOT NULL AND center_lng IS NOT NULL
                AND tenant_id = :tenant",
            [':tenant' => defined('DEFAULT_TENANT_ID') ? DEFAULT_TENANT_ID : 1]
        ) ?: [];
    }

    public function create(array $d, int $uid): array
    {
        $name = $this->s($d['name'] ?? '');
        if ($name === '') return ['ok' => false, 'msg' => 'Geofence name is required.', 'id' => 0];
        $lat  = is_numeric($d['center_lat'] ?? null) ? (float)$d['center_lat'] : null;
        $lng  = is_numeric($d['center_lng'] ?? null) ? (float)$d['center_lng'] : null;
        $rad  = is_numeric($d['radius_m']   ?? null) ? (int)$d['radius_m']     : null;
        if ($lat === null || $lng === null || !$rad || $rad < 10 || $rad > 200000) {
            return ['ok' => false, 'msg' => 'Center coordinates and radius (10 m – 200 km) are required.', 'id' => 0];
        }

        $r = (new DB())->insertUpdate(
            "INSERT INTO geofences
                (tenant_id, name, kind, geometry_json, center_lat, center_lng, radius_m,
                 color, active, customer_id, trigger_alert, notes, created_by, created_on)
             VALUES
                (:tenant, :name, :kd, :geo, :lat, :lng, :rad,
                 :col, :ac, :cust, :ta, :notes, :uid, NOW())",
            [
                ':tenant' => defined('DEFAULT_TENANT_ID') ? DEFAULT_TENANT_ID : 1,
                ':name' => $name,
                ':kd'   => $this->s(($d['kind'] ?? '') ?: 'depot'),
                ':geo'  => json_encode(['type' => 'circle', 'center' => [$lat, $lng], 'radius_m' => $rad]),
                ':lat'  => $lat,
                ':lng'  => $lng,
                ':rad'  => $rad,
                ':col'  => $this->s(($d['color'] ?? '') ?: '#2626F5'),
                ':ac'   => !empty($d['active']) ? 1 : 0,
                ':cust' => !empty($d['customer_id']) ? (int)$d['customer_id'] : null,
                ':ta'   => !empty($d['trigger_alert']) ? 1 : 0,
                ':notes'=> $this->s($d['notes'] ?? ''),
                ':uid'  => $uid,
            ]
        );
        return ['ok' => $r['success'] > 0, 'msg' => $r['success'] > 0 ? 'Geofence created.' : 'Save failed.', 'id' => (int)$r['last_id']];
    }

    public function update(int $id, array $d, int $uid): array
    {
        $existing = $this->byId($id);
        if (!$existing) return ['ok' => false, 'msg' => 'Geofence not found.', 'id' => 0];
        $name = $this->s($d['name'] ?? '');
        if ($name === '') return ['ok' => false, 'msg' => 'Name is required.', 'id' => 0];
        $lat  = is_numeric($d['center_lat'] ?? null) ? (float)$d['center_lat'] : null;
        $lng  = is_numeric($d['center_lng'] ?? null) ? (float)$d['center_lng'] : null;
        $rad  = is_numeric($d['radius_m']   ?? null) ? (int)$d['radius_m']     : null;
        if ($lat === null || $lng === null || !$rad) return ['ok' => false, 'msg' => 'Coordinates + radius required.', 'id' => 0];

        (new DB())->insertUpdate(
            "UPDATE geofences SET
                name = :name, kind = :kd, geometry_json = :geo,
                center_lat = :lat, center_lng = :lng, radius_m = :rad,
                color = :col, active = :ac, customer_id = :cust, trigger_alert = :ta,
                notes = :notes, edited_by = :uid, edited_on = NOW()
              WHERE c0d = :id AND deleted = 0",
            [
                ':name' => $name,
                ':kd'   => $this->s(($d['kind'] ?? '') ?: 'depot'),
                ':geo'  => json_encode(['type' => 'circle', 'center' => [$lat, $lng], 'radius_m' => $rad]),
                ':lat'  => $lat,
                ':lng'  => $lng,
                ':rad'  => $rad,
                ':col'  => $this->s(($d['color'] ?? '') ?: '#2626F5'),
                ':ac'   => !empty($d['active']) ? 1 : 0,
                ':cust' => !empty($d['customer_id']) ? (int)$d['customer_id'] : null,
                ':ta'   => !empty($d['trigger_alert']) ? 1 : 0,
                ':notes'=> $this->s($d['notes'] ?? ''),
                ':uid'  => $uid,
                ':id'   => $id,
            ]
        );
        return ['ok' => true, 'msg' => 'Geofence updated.', 'id' => $id];
    }

    public function softDelete(int $id, int $uid): array
    {
        (new DB())->insertUpdate(
            "UPDATE geofences SET deleted = 1, edited_by = :uid, edited_on = NOW() WHERE c0d = :id",
            [':uid' => $uid, ':id' => $id]
        );
        return ['ok' => true];
    }

    /**
     * Return every active fence containing the point. Distances via Haversine.
     * Called from GpsPositions::ingest() — must stay cheap.
     * Cache the fence list on this instance so we don't refetch per position
     * inside a batch call.
     */
    private ?array $_activeCache = null;
    public function matchPoint(float $lat, float $lng): array
    {
        if ($this->_activeCache === null) $this->_activeCache = $this->activeAll();
        $inside = [];
        foreach ($this->_activeCache as $f) {
            $d = self::haversineMetres((float)$f['center_lat'], (float)$f['center_lng'], $lat, $lng);
            if ($d <= (int)$f['radius_m']) $inside[] = $f;
        }
        return $inside;
    }

    public static function haversineMetres(float $lat1, float $lng1, float $lat2, float $lng2): float
    {
        $R = 6371000.0;
        $dLat = deg2rad($lat2 - $lat1);
        $dLng = deg2rad($lng2 - $lng1);
        $a = sin($dLat / 2) ** 2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;
        return $R * 2 * atan2(sqrt($a), sqrt(1 - $a));
    }
}
