const crypto = require('crypto');
const db = require('../db');
const config = require('../config');
const { requireAccess, requireOwner, writeAudit } = require('../middleware/access');
const { haversine, deviceStatus, AppError } = require('../utils/helpers');

async function listVehicles(userId) {
  const [rows] = await db.query(
    `SELECT d.id, d.public_id, d.name, d.status, d.last_seen, d.battery_percent, 'owner' AS role
     FROM devices d
     JOIN device_ownership o ON o.device_id = d.id AND o.ended_at IS NULL AND o.owner_id = ?
     UNION
     SELECT d.id, d.public_id, d.name, d.status, d.last_seen, d.battery_percent, 'viewer' AS role
     FROM devices d
     JOIN device_sharing s ON s.device_id = d.id AND s.viewer_id = ?`,
    [userId, userId],
  );

  const vehicles = [];
  for (const row of rows) {
    row.status = deviceStatus(row.last_seen, config.offlineAfterSeconds);
    await db.query('UPDATE devices SET status = ? WHERE id = ?', [row.status, row.id]);
    const [locs] = await db.query(
      `SELECT lat, lng, speed, direction, accuracy, recorded_at
       FROM locations WHERE device_id = ? ORDER BY recorded_at DESC LIMIT 1`,
      [row.id],
    );
    vehicles.push({ ...row, last_location: locs[0] || null });
  }
  return { vehicles };
}

async function activateDevice(userId, { activation_token, name }) {
  const token = String(activation_token || '').trim();
  const vehicleName = String(name || 'My Vehicle').trim() || 'My Vehicle';
  const conn = await db.getConnection();

  try {
    await conn.beginTransaction();
    const [acts] = await conn.query(
      'SELECT * FROM device_activation WHERE token = ? FOR UPDATE',
      [token],
    );
    if (!acts[0] || acts[0].status !== 'NEW') {
      throw new AppError(400, 'Invalid or already used activation code');
    }

    await conn.query(
      'UPDATE device_activation SET status = "USED", activated_at = NOW() WHERE id = ?',
      [acts[0].id],
    );
    await conn.query('INSERT INTO device_ownership (device_id, owner_id) VALUES (?,?)', [
      acts[0].device_id,
      userId,
    ]);
    await conn.query('UPDATE devices SET name = ? WHERE id = ?', [vehicleName, acts[0].device_id]);
    await conn.query(
      `INSERT INTO subscriptions (user_id, plan, starts_at, ends_at)
       VALUES (?, "annual", CURDATE(), DATE_ADD(CURDATE(), INTERVAL 1 YEAR))`,
      [userId],
    );
    await conn.query(
      'INSERT INTO audit_log (action, user_id, device_id, detail) VALUES (?,?,?,?)',
      ['activate', userId, acts[0].device_id, 'QR activation'],
    );
    await conn.commit();
    return { ok: true, device_id: acts[0].device_id };
  } catch (err) {
    await conn.rollback();
    throw err;
  } finally {
    conn.release();
  }
}

async function getLive(userId, deviceId) {
  const role = await requireAccess(userId, deviceId);
  const [devs] = await db.query(
    'SELECT id, public_id, name, status, last_seen, battery_percent FROM devices WHERE id = ?',
    [deviceId],
  );
  if (!devs[0]) throw new AppError(404, 'Device not found');

  devs[0].status = deviceStatus(devs[0].last_seen, config.offlineAfterSeconds);
  const [locs] = await db.query(
    `SELECT lat, lng, speed, direction, accuracy, recorded_at
     FROM locations WHERE device_id = ? ORDER BY recorded_at DESC LIMIT 1`,
    [deviceId],
  );
  const [locks] = await db.query(
    'SELECT lat, lng, radius_m, locked_at, status FROM position_locks WHERE device_id = ?',
    [deviceId],
  );

  return {
    device: devs[0],
    role,
    location: locs[0] || null,
    position_lock: locks[0] || null,
  };
}

async function getHistory(userId, deviceId, from, to) {
  await requireAccess(userId, deviceId);
  const start = from || `${new Date().toISOString().slice(0, 10)} 00:00:00`;
  const end = to || `${new Date().toISOString().slice(0, 10)} 23:59:59`;

  const [points] = await db.query(
    `SELECT lat, lng, speed, direction, recorded_at
     FROM locations
     WHERE device_id = ? AND recorded_at BETWEEN ? AND ?
     ORDER BY recorded_at ASC`,
    [deviceId, start, end],
  );

  let distance = 0;
  for (let i = 1; i < points.length; i++) {
    distance += haversine(
      Number(points[i - 1].lat),
      Number(points[i - 1].lng),
      Number(points[i].lat),
      Number(points[i].lng),
    );
  }
  return { points, distance_m: distance };
}

async function listGeofences(userId, deviceId) {
  await requireAccess(userId, deviceId);
  const [rows] = await db.query(
    'SELECT id, name, lat, lng, radius_m, active FROM geofences WHERE device_id = ?',
    [deviceId],
  );
  return { geofences: rows };
}

async function createGeofence(userId, deviceId, body) {
  await requireOwner(userId, deviceId);
  await db.query(
    'INSERT INTO geofences (device_id, name, lat, lng, radius_m) VALUES (?,?,?,?,?)',
    [deviceId, body.name || 'Home', body.lat, body.lng, body.radius_m || 200],
  );
  return { ok: true };
}

async function setPositionLock(userId, deviceId, body) {
  await requireOwner(userId, deviceId);
  const on = body.on !== false;

  if (!on) {
    await db.query(
      'UPDATE position_locks SET status = "OFF", consecutive_outside = 0 WHERE device_id = ?',
      [deviceId],
    );
    return { ok: true, status: 'OFF' };
  }

  const [locs] = await db.query(
    'SELECT lat, lng FROM locations WHERE device_id = ? ORDER BY recorded_at DESC LIMIT 1',
    [deviceId],
  );
  if (!locs[0]) throw new AppError(400, 'No GPS fix yet');

  let radius = Number(body.radius_m || 100);
  if (![50, 100, 200, 500].includes(radius)) radius = 100;

  await db.query(
    `INSERT INTO position_locks (device_id, lat, lng, radius_m, locked_at, status, consecutive_outside)
     VALUES (?,?,?,?,NOW(),"ON",0)
     ON DUPLICATE KEY UPDATE
       lat=VALUES(lat), lng=VALUES(lng), radius_m=VALUES(radius_m),
       locked_at=NOW(), status="ON", consecutive_outside=0`,
    [deviceId, locs[0].lat, locs[0].lng, radius],
  );

  return {
    ok: true,
    status: 'ON',
    lat: locs[0].lat,
    lng: locs[0].lng,
    radius_m: radius,
  };
}

async function listAlerts(userId, deviceId) {
  await requireAccess(userId, deviceId);
  const [rows] = await db.query(
    'SELECT * FROM alerts WHERE device_id = ? ORDER BY created_at DESC LIMIT 100',
    [deviceId],
  );
  return { alerts: rows };
}

async function shareDevice(userId, deviceId, mobile) {
  await requireOwner(userId, deviceId);
  const number = String(mobile || '').trim();
  const [users] = await db.query('SELECT id FROM users WHERE mobile = ?', [number]);
  if (!users[0]) throw new AppError(404, 'That mobile is not registered yet');
  if (users[0].id === userId) throw new AppError(400, 'You already own this vehicle');

  await db.query(
    'INSERT IGNORE INTO device_sharing (device_id, owner_id, viewer_id) VALUES (?,?,?)',
    [deviceId, userId, users[0].id],
  );
  await writeAudit('share', userId, deviceId, number);
  return { ok: true };
}

async function createTransfer(userId, deviceId) {
  await requireOwner(userId, deviceId);
  const transferToken = crypto.randomBytes(24).toString('hex');

  await db.query(
    'UPDATE transfer_requests SET status = "CANCELLED" WHERE device_id = ? AND status = "OPEN"',
    [deviceId],
  );
  await db.query(
    `INSERT INTO transfer_requests (device_id, old_owner_id, token, expires_at)
     VALUES (?,?,?, DATE_ADD(NOW(), INTERVAL 24 HOUR))`,
    [deviceId, userId, transferToken],
  );
  await writeAudit('transfer_create', userId, deviceId, null);
  return { transfer_token: transferToken, expires_hours: 24 };
}

async function claimTransfer(userId, transferToken) {
  const token = String(transferToken || '').trim();
  const conn = await db.getConnection();

  try {
    await conn.beginTransaction();
    const [reqs] = await conn.query(
      'SELECT * FROM transfer_requests WHERE token = ? FOR UPDATE',
      [token],
    );
    const row = reqs[0];
    if (!row || row.status !== 'OPEN' || new Date(row.expires_at) < new Date()) {
      throw new AppError(400, 'Invalid or expired transfer code');
    }
    if (row.old_owner_id === userId) {
      throw new AppError(400, 'New owner must be a different account');
    }

    await conn.query(
      'UPDATE device_ownership SET ended_at = NOW() WHERE device_id = ? AND ended_at IS NULL',
      [row.device_id],
    );
    await conn.query('DELETE FROM device_sharing WHERE device_id = ?', [row.device_id]);
    await conn.query('INSERT INTO device_ownership (device_id, owner_id) VALUES (?,?)', [
      row.device_id,
      userId,
    ]);
    await conn.query('UPDATE transfer_requests SET status = "USED" WHERE id = ?', [row.id]);
    await conn.query(
      'INSERT INTO audit_log (action, user_id, device_id, detail) VALUES (?,?,?,?)',
      ['transfer_complete', userId, row.device_id, null],
    );
    await conn.commit();
    return { ok: true };
  } catch (err) {
    await conn.rollback();
    throw err;
  } finally {
    conn.release();
  }
}

module.exports = {
  listVehicles,
  activateDevice,
  getLive,
  getHistory,
  listGeofences,
  createGeofence,
  setPositionLock,
  listAlerts,
  shareDevice,
  createTransfer,
  claimTransfer,
};
