-- ===========================================
-- MECANICAS DIESEL - Schema gps_tracking
-- ===========================================
-- Integracion GPS, posiciones, geocercas, rutas
-- Modulo: MMD-014 GPS Integration

-- -------------------------------------------
-- SCHEMA CREATION
-- -------------------------------------------
CREATE SCHEMA IF NOT EXISTS gps_tracking;
COMMENT ON SCHEMA gps_tracking IS 'Tracking GPS, posiciones en tiempo real, geocercas y calculo de rutas';

GRANT USAGE ON SCHEMA gps_tracking TO mecanicas_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA gps_tracking TO mecanicas_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA gps_tracking GRANT ALL ON TABLES TO mecanicas_user;

SET search_path TO gps_tracking, public;

-- -------------------------------------------
-- GPS_DEVICES - Dispositivos GPS vinculados a unidades
-- -------------------------------------------
CREATE TABLE gps_tracking.gps_devices (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,

    -- Vinculacion a unidad de flota
    unit_id UUID NOT NULL,  -- FK a vehicle_management.vehicles
    unit_type VARCHAR(20) DEFAULT 'vehicle'
        CHECK (unit_type IN ('vehicle', 'trailer', 'equipment', 'technician')),

    -- Identificacion externa (plataforma GPS)
    external_device_id VARCHAR(100) NOT NULL,
    platform VARCHAR(30) NOT NULL DEFAULT 'traccar'
        CHECK (platform IN ('traccar', 'wialon', 'samsara', 'geotab', 'manual')),

    -- Identificadores del dispositivo
    imei VARCHAR(20),
    serial_number VARCHAR(50),
    phone_number VARCHAR(20),
    model VARCHAR(50),
    manufacturer VARCHAR(50),

    -- Estado
    is_active BOOLEAN DEFAULT TRUE,
    last_position_at TIMESTAMP WITH TIME ZONE,
    last_position_lat DECIMAL(10, 7),
    last_position_lng DECIMAL(10, 7),

    -- Configuracion
    position_interval_seconds INTEGER DEFAULT 30 CHECK (position_interval_seconds >= 5),
    metadata JSONB DEFAULT '{}',

    -- Auditoria
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    created_by UUID,

    CONSTRAINT uq_device_external UNIQUE (tenant_id, platform, external_device_id),
    CONSTRAINT uq_device_unit UNIQUE (tenant_id, unit_id)
);

CREATE INDEX idx_gps_devices_tenant ON gps_tracking.gps_devices(tenant_id);
CREATE INDEX idx_gps_devices_unit ON gps_tracking.gps_devices(unit_id);
CREATE INDEX idx_gps_devices_external ON gps_tracking.gps_devices(external_device_id);
CREATE INDEX idx_gps_devices_platform ON gps_tracking.gps_devices(platform);
CREATE INDEX idx_gps_devices_active ON gps_tracking.gps_devices(is_active) WHERE is_active = TRUE;

CREATE TRIGGER trg_gps_devices_updated_at
    BEFORE UPDATE ON gps_tracking.gps_devices
    FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();

SELECT create_tenant_rls_policies('gps_tracking', 'gps_devices');

COMMENT ON TABLE gps_tracking.gps_devices IS 'Dispositivos GPS vinculados a unidades de la flota';
COMMENT ON COLUMN gps_tracking.gps_devices.external_device_id IS 'ID del dispositivo en la plataforma GPS externa (Traccar, etc.)';
COMMENT ON COLUMN gps_tracking.gps_devices.platform IS 'Plataforma GPS: traccar (recomendado), wialon, samsara, geotab, manual';

-- -------------------------------------------
-- GPS_POSITIONS - Posiciones registradas (time-series)
-- -------------------------------------------
CREATE TABLE gps_tracking.gps_positions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,

    device_id UUID NOT NULL REFERENCES gps_tracking.gps_devices(id) ON DELETE CASCADE,
    unit_id UUID NOT NULL,  -- Denormalizado para queries rapidos

    -- Posicion
    latitude DECIMAL(10, 7) NOT NULL CHECK (latitude >= -90 AND latitude <= 90),
    longitude DECIMAL(10, 7) NOT NULL CHECK (longitude >= -180 AND longitude <= 180),
    altitude DECIMAL(8, 2),  -- metros sobre nivel del mar

    -- Movimiento
    speed DECIMAL(6, 2) CHECK (speed >= 0),  -- km/h
    course DECIMAL(5, 2) CHECK (course >= 0 AND course < 360),  -- grados (0 = norte)

    -- Precision
    accuracy DECIMAL(6, 2) CHECK (accuracy >= 0),  -- metros
    hdop DECIMAL(4, 2),  -- Horizontal Dilution of Precision

    -- Atributos adicionales (del dispositivo)
    attributes JSONB DEFAULT '{}',
    -- attributes puede contener: ignition, fuel, odometer, engineHours, batteryLevel, etc.

    -- Timestamps
    device_time TIMESTAMP WITH TIME ZONE NOT NULL,  -- Hora del dispositivo
    server_time TIMESTAMP WITH TIME ZONE DEFAULT NOW(),  -- Hora de recepcion
    fix_time TIMESTAMP WITH TIME ZONE,  -- Hora del fix GPS

    -- Validacion
    is_valid BOOLEAN DEFAULT TRUE,  -- Si paso filtros anti-ruido

    -- Indices para busquedas
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Particionar por fecha si hay muchos registros
-- CREATE TABLE gps_tracking.gps_positions_YYYY_MM PARTITION OF gps_tracking.gps_positions
-- FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

CREATE INDEX idx_gps_positions_tenant ON gps_tracking.gps_positions(tenant_id);
CREATE INDEX idx_gps_positions_device ON gps_tracking.gps_positions(device_id);
CREATE INDEX idx_gps_positions_unit ON gps_tracking.gps_positions(unit_id);
CREATE INDEX idx_gps_positions_device_time ON gps_tracking.gps_positions(device_id, device_time DESC);
CREATE INDEX idx_gps_positions_time ON gps_tracking.gps_positions(device_time DESC);
CREATE INDEX idx_gps_positions_valid ON gps_tracking.gps_positions(is_valid) WHERE is_valid = TRUE;

-- Indice espacial para queries geograficos
CREATE INDEX idx_gps_positions_location ON gps_tracking.gps_positions
    USING GIST (ST_SetSRID(ST_MakePoint(longitude, latitude), 4326))
    WHERE longitude IS NOT NULL AND latitude IS NOT NULL;

SELECT create_tenant_rls_policies('gps_tracking', 'gps_positions');

COMMENT ON TABLE gps_tracking.gps_positions IS 'Posiciones GPS registradas - datos time-series';
COMMENT ON COLUMN gps_tracking.gps_positions.attributes IS 'Atributos adicionales: ignition, fuel, odometer, engineHours, etc.';
COMMENT ON COLUMN gps_tracking.gps_positions.is_valid IS 'Si la posicion paso los filtros anti-ruido';

-- -------------------------------------------
-- GEOFENCES - Geocercas (areas de interes)
-- -------------------------------------------
CREATE TABLE gps_tracking.geofences (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,

    -- Identificacion
    name VARCHAR(100) NOT NULL,
    code VARCHAR(30),
    description TEXT,

    -- Tipo de geocerca
    geofence_type VARCHAR(20) NOT NULL DEFAULT 'circle'
        CHECK (geofence_type IN ('circle', 'polygon')),

    -- Para tipo CIRCLE
    center_lat DECIMAL(10, 7),
    center_lng DECIMAL(10, 7),
    radius_meters DECIMAL(10, 2) CHECK (radius_meters > 0),

    -- Para tipo POLYGON (formato GeoJSON)
    polygon_geojson JSONB,
    -- Ejemplo: {"type": "Polygon", "coordinates": [[[lng1,lat1],[lng2,lat2],...]]}

    -- Geometria PostGIS (calculada)
    geometry GEOMETRY(Geometry, 4326),

    -- Categoria operativa
    category VARCHAR(30) NOT NULL DEFAULT 'custom'
        CHECK (category IN ('base', 'coverage', 'restricted', 'high_risk', 'client', 'custom')),

    -- Configuracion de eventos
    trigger_on_enter BOOLEAN DEFAULT TRUE,
    trigger_on_exit BOOLEAN DEFAULT TRUE,
    dwell_time_seconds INTEGER DEFAULT 0,  -- Tiempo minimo para disparar evento

    -- Estado
    is_active BOOLEAN DEFAULT TRUE,
    color VARCHAR(7) DEFAULT '#3388ff',  -- Color en mapa (hex)
    metadata JSONB DEFAULT '{}',

    -- Auditoria
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    created_by UUID
);

CREATE INDEX idx_geofences_tenant ON gps_tracking.geofences(tenant_id);
CREATE INDEX idx_geofences_category ON gps_tracking.geofences(category);
CREATE INDEX idx_geofences_active ON gps_tracking.geofences(is_active) WHERE is_active = TRUE;

-- Indice espacial
CREATE INDEX idx_geofences_geometry ON gps_tracking.geofences USING GIST (geometry);

CREATE TRIGGER trg_geofences_updated_at
    BEFORE UPDATE ON gps_tracking.geofences
    FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();

SELECT create_tenant_rls_policies('gps_tracking', 'geofences');

COMMENT ON TABLE gps_tracking.geofences IS 'Geocercas para detectar entrada/salida de areas';
COMMENT ON COLUMN gps_tracking.geofences.category IS 'Categoria: base (taller), coverage (zona atendida), restricted, high_risk, client, custom';
COMMENT ON COLUMN gps_tracking.geofences.dwell_time_seconds IS 'Segundos minimos dentro/fuera para disparar evento';

-- Trigger para calcular geometria automaticamente
CREATE OR REPLACE FUNCTION gps_tracking.calculate_geofence_geometry()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.geofence_type = 'circle' AND NEW.center_lat IS NOT NULL AND NEW.center_lng IS NOT NULL THEN
        -- Crear circulo
        NEW.geometry := ST_Buffer(
            ST_SetSRID(ST_MakePoint(NEW.center_lng, NEW.center_lat), 4326)::geography,
            NEW.radius_meters
        )::geometry;
    ELSIF NEW.geofence_type = 'polygon' AND NEW.polygon_geojson IS NOT NULL THEN
        -- Crear poligono desde GeoJSON
        NEW.geometry := ST_SetSRID(ST_GeomFromGeoJSON(NEW.polygon_geojson::text), 4326);
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_geofences_geometry
    BEFORE INSERT OR UPDATE ON gps_tracking.geofences
    FOR EACH ROW EXECUTE FUNCTION gps_tracking.calculate_geofence_geometry();

-- -------------------------------------------
-- GEOFENCE_EVENTS - Eventos de entrada/salida
-- -------------------------------------------
CREATE TABLE gps_tracking.geofence_events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,

    geofence_id UUID NOT NULL REFERENCES gps_tracking.geofences(id) ON DELETE CASCADE,
    device_id UUID NOT NULL REFERENCES gps_tracking.gps_devices(id) ON DELETE CASCADE,
    unit_id UUID NOT NULL,

    -- Tipo de evento
    event_type VARCHAR(10) NOT NULL CHECK (event_type IN ('enter', 'exit', 'dwell')),

    -- Posicion que disparo el evento
    position_id UUID REFERENCES gps_tracking.gps_positions(id),
    latitude DECIMAL(10, 7) NOT NULL,
    longitude DECIMAL(10, 7) NOT NULL,

    -- Timestamps
    event_time TIMESTAMP WITH TIME ZONE NOT NULL,
    processed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    -- Vinculacion a operaciones
    related_incident_id UUID,  -- FK a rescue_order si aplica

    -- Metadata
    metadata JSONB DEFAULT '{}'
);

CREATE INDEX idx_geofence_events_tenant ON gps_tracking.geofence_events(tenant_id);
CREATE INDEX idx_geofence_events_geofence ON gps_tracking.geofence_events(geofence_id);
CREATE INDEX idx_geofence_events_device ON gps_tracking.geofence_events(device_id);
CREATE INDEX idx_geofence_events_unit ON gps_tracking.geofence_events(unit_id);
CREATE INDEX idx_geofence_events_time ON gps_tracking.geofence_events(event_time DESC);
CREATE INDEX idx_geofence_events_incident ON gps_tracking.geofence_events(related_incident_id)
    WHERE related_incident_id IS NOT NULL;

SELECT create_tenant_rls_policies('gps_tracking', 'geofence_events');

COMMENT ON TABLE gps_tracking.geofence_events IS 'Eventos de entrada/salida de geocercas';

-- -------------------------------------------
-- ROUTE_SEGMENTS - Segmentos de ruta para km facturables
-- -------------------------------------------
CREATE TABLE gps_tracking.route_segments (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,

    -- Vinculacion a incidente/servicio
    incident_id UUID,  -- FK a rescue_order
    unit_id UUID NOT NULL,
    device_id UUID REFERENCES gps_tracking.gps_devices(id),

    -- Posiciones de inicio/fin
    start_position_id UUID REFERENCES gps_tracking.gps_positions(id),
    end_position_id UUID REFERENCES gps_tracking.gps_positions(id),

    -- Coordenadas (denormalizadas)
    start_lat DECIMAL(10, 7) NOT NULL,
    start_lng DECIMAL(10, 7) NOT NULL,
    end_lat DECIMAL(10, 7) NOT NULL,
    end_lng DECIMAL(10, 7) NOT NULL,

    -- Distancias
    distance_km DECIMAL(10, 3) NOT NULL CHECK (distance_km >= 0),
    raw_distance_km DECIMAL(10, 3),  -- Antes de filtros

    -- Tiempos
    start_time TIMESTAMP WITH TIME ZONE NOT NULL,
    end_time TIMESTAMP WITH TIME ZONE NOT NULL,
    duration_minutes DECIMAL(8, 2) CHECK (duration_minutes >= 0),

    -- Tipo de segmento
    segment_type VARCHAR(30) NOT NULL DEFAULT 'other'
        CHECK (segment_type IN ('to_incident', 'at_incident', 'return', 'between_incidents', 'other')),

    -- Validacion
    is_valid BOOLEAN DEFAULT TRUE,
    validation_notes TEXT,

    -- Polyline codificada (para visualizacion)
    encoded_polyline TEXT,

    -- Metadata
    metadata JSONB DEFAULT '{}',

    -- Auditoria
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    calculated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    CONSTRAINT chk_segment_times CHECK (end_time >= start_time)
);

CREATE INDEX idx_route_segments_tenant ON gps_tracking.route_segments(tenant_id);
CREATE INDEX idx_route_segments_incident ON gps_tracking.route_segments(incident_id);
CREATE INDEX idx_route_segments_unit ON gps_tracking.route_segments(unit_id);
CREATE INDEX idx_route_segments_type ON gps_tracking.route_segments(segment_type);
CREATE INDEX idx_route_segments_time ON gps_tracking.route_segments(start_time DESC);
CREATE INDEX idx_route_segments_valid ON gps_tracking.route_segments(is_valid) WHERE is_valid = TRUE;

SELECT create_tenant_rls_policies('gps_tracking', 'route_segments');

COMMENT ON TABLE gps_tracking.route_segments IS 'Segmentos de ruta calculados para facturacion de km';
COMMENT ON COLUMN gps_tracking.route_segments.segment_type IS 'Tipo: to_incident (ida), at_incident (en sitio), return (regreso), between_incidents, other';
COMMENT ON COLUMN gps_tracking.route_segments.is_valid IS 'Si el segmento paso validacion anti-ruido';
COMMENT ON COLUMN gps_tracking.route_segments.encoded_polyline IS 'Polyline codificada (Google format) para visualizar en mapa';

-- -------------------------------------------
-- FUNCTIONS: Calculo de distancias
-- -------------------------------------------

-- Funcion para calcular distancia entre dos puntos (Haversine)
CREATE OR REPLACE FUNCTION gps_tracking.haversine_distance(
    lat1 DECIMAL, lng1 DECIMAL,
    lat2 DECIMAL, lng2 DECIMAL
)
RETURNS DECIMAL AS $$
DECLARE
    R DECIMAL := 6371;  -- Radio de la Tierra en km
    dlat DECIMAL;
    dlng DECIMAL;
    a DECIMAL;
    c DECIMAL;
BEGIN
    dlat := RADIANS(lat2 - lat1);
    dlng := RADIANS(lng2 - lng1);

    a := SIN(dlat/2) * SIN(dlat/2) +
         COS(RADIANS(lat1)) * COS(RADIANS(lat2)) *
         SIN(dlng/2) * SIN(dlng/2);

    c := 2 * ATAN2(SQRT(a), SQRT(1-a));

    RETURN R * c;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

COMMENT ON FUNCTION gps_tracking.haversine_distance IS 'Calcula distancia en km entre dos puntos usando formula Haversine';

-- Funcion para calcular distancia total de una ruta (array de posiciones)
CREATE OR REPLACE FUNCTION gps_tracking.calculate_route_distance(
    p_device_id UUID,
    p_start_time TIMESTAMP WITH TIME ZONE,
    p_end_time TIMESTAMP WITH TIME ZONE
)
RETURNS TABLE (
    total_distance_km DECIMAL,
    position_count INTEGER,
    duration_minutes DECIMAL
) AS $$
DECLARE
    v_prev_lat DECIMAL;
    v_prev_lng DECIMAL;
    v_prev_time TIMESTAMP WITH TIME ZONE;
    v_distance DECIMAL := 0;
    v_count INTEGER := 0;
    v_rec RECORD;
BEGIN
    FOR v_rec IN
        SELECT latitude, longitude, device_time
        FROM gps_tracking.gps_positions
        WHERE device_id = p_device_id
          AND device_time BETWEEN p_start_time AND p_end_time
          AND is_valid = TRUE
        ORDER BY device_time
    LOOP
        IF v_prev_lat IS NOT NULL THEN
            v_distance := v_distance + gps_tracking.haversine_distance(
                v_prev_lat, v_prev_lng,
                v_rec.latitude, v_rec.longitude
            );
        ELSE
            v_prev_time := v_rec.device_time;
        END IF;

        v_prev_lat := v_rec.latitude;
        v_prev_lng := v_rec.longitude;
        v_count := v_count + 1;
    END LOOP;

    RETURN QUERY SELECT
        ROUND(v_distance, 3)::DECIMAL,
        v_count,
        ROUND(EXTRACT(EPOCH FROM (v_rec.device_time - v_prev_time)) / 60, 2)::DECIMAL;
END;
$$ LANGUAGE plpgsql STABLE;

COMMENT ON FUNCTION gps_tracking.calculate_route_distance IS 'Calcula distancia total de una ruta entre dos timestamps';

-- Funcion para verificar si un punto esta dentro de una geocerca
CREATE OR REPLACE FUNCTION gps_tracking.is_point_in_geofence(
    p_lat DECIMAL,
    p_lng DECIMAL,
    p_geofence_id UUID
)
RETURNS BOOLEAN AS $$
DECLARE
    v_geofence gps_tracking.geofences;
BEGIN
    SELECT * INTO v_geofence
    FROM gps_tracking.geofences
    WHERE id = p_geofence_id AND is_active = TRUE;

    IF v_geofence IS NULL THEN
        RETURN FALSE;
    END IF;

    -- Usar PostGIS para verificacion precisa
    RETURN ST_Contains(
        v_geofence.geometry,
        ST_SetSRID(ST_MakePoint(p_lng, p_lat), 4326)
    );
END;
$$ LANGUAGE plpgsql STABLE;

COMMENT ON FUNCTION gps_tracking.is_point_in_geofence IS 'Verifica si un punto esta dentro de una geocerca';

-- -------------------------------------------
-- VIEWS
-- -------------------------------------------

-- Vista de ultima posicion por unidad
CREATE OR REPLACE VIEW gps_tracking.vw_last_positions AS
SELECT DISTINCT ON (d.unit_id)
    d.id as device_id,
    d.tenant_id,
    d.unit_id,
    d.external_device_id,
    d.platform,
    d.is_active,
    p.id as position_id,
    p.latitude,
    p.longitude,
    p.speed,
    p.course,
    p.attributes,
    p.device_time,
    p.is_valid,
    EXTRACT(EPOCH FROM (NOW() - p.device_time)) / 60 as minutes_ago
FROM gps_tracking.gps_devices d
LEFT JOIN gps_tracking.gps_positions p ON d.id = p.device_id
WHERE d.is_active = TRUE
  AND d.tenant_id = get_current_tenant_id()
ORDER BY d.unit_id, p.device_time DESC;

COMMENT ON VIEW gps_tracking.vw_last_positions IS 'Vista de ultima posicion conocida por unidad con filtro RLS';

-- Vista de resumen de dispositivos
CREATE OR REPLACE VIEW gps_tracking.vw_device_summary AS
SELECT
    d.id,
    d.tenant_id,
    d.unit_id,
    d.external_device_id,
    d.platform,
    d.model,
    d.is_active,
    d.last_position_at,
    d.last_position_lat,
    d.last_position_lng,
    CASE
        WHEN d.last_position_at IS NULL THEN 'never'
        WHEN NOW() - d.last_position_at < INTERVAL '5 minutes' THEN 'online'
        WHEN NOW() - d.last_position_at < INTERVAL '1 hour' THEN 'recent'
        ELSE 'offline'
    END as connection_status,
    (SELECT COUNT(*) FROM gps_tracking.gps_positions p
     WHERE p.device_id = d.id AND p.device_time > NOW() - INTERVAL '24 hours') as positions_24h
FROM gps_tracking.gps_devices d
WHERE d.tenant_id = get_current_tenant_id();

COMMENT ON VIEW gps_tracking.vw_device_summary IS 'Vista resumida de dispositivos GPS con estado de conexion';

-- -------------------------------------------
-- TRIGGER: Actualizar ultima posicion en device
-- -------------------------------------------
CREATE OR REPLACE FUNCTION gps_tracking.update_device_last_position()
RETURNS TRIGGER AS $$
BEGIN
    UPDATE gps_tracking.gps_devices
    SET
        last_position_at = NEW.device_time,
        last_position_lat = NEW.latitude,
        last_position_lng = NEW.longitude
    WHERE id = NEW.device_id
      AND (last_position_at IS NULL OR last_position_at < NEW.device_time);

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_update_device_last_position
    AFTER INSERT ON gps_tracking.gps_positions
    FOR EACH ROW EXECUTE FUNCTION gps_tracking.update_device_last_position();

-- -------------------------------------------
-- DATOS INICIALES: Geocercas base
-- -------------------------------------------
-- NOTA: Ejecutar despues de tener tenant_id valido
-- INSERT INTO gps_tracking.geofences (tenant_id, name, code, geofence_type, center_lat, center_lng, radius_meters, category)
-- VALUES
--   ('{tenant_id}', 'Taller Principal', 'BASE-01', 'circle', 19.4326, -99.1332, 200, 'base');

COMMENT ON SCHEMA gps_tracking IS 'Schema completo para tracking GPS - MMD-014';
