import { db, id, json, now, parseJson, visibleClause } from '../db.js';
import { hostGroupSchema } from '../forms/schemas.js';
import { camelHostGroup } from './mappers.js';
import { filterVisibleHostIds } from './hostModel.js';

function scopedGroupId(payload) {
  return payload.visibility === 'group' ? payload.groupId || null : null;
}

function normalizeHostGroup(body) {
  const parsed = hostGroupSchema.parse(body || {});
  return {
    ...parsed,
    groupId: scopedGroupId(parsed),
    rules: parsed.mode === 'dynamic' ? parsed.rules : [],
    hostIds: parsed.mode === 'manual' ? parsed.hostIds : []
  };
}

export function listHostGroups(userId) {
  return db.prepare(`
    SELECT hg.*, g.name AS group_name,
      COALESCE((SELECT COUNT(*) FROM host_group_members hgm WHERE hgm.host_group_id = hg.id), 0) AS member_count,
      COALESCE((SELECT json_group_array(host_id) FROM host_group_members hgm WHERE hgm.host_group_id = hg.id), '[]') AS host_ids
    FROM host_groups hg
    LEFT JOIN groups g ON g.id = hg.group_id
    WHERE ${visibleClause('hg')}
    ORDER BY hg.name
  `).all(userId, userId).map(camelHostGroup);
}

export function getVisibleHostGroup(groupId, userId) {
  const row = db.prepare(`
    SELECT hg.*, g.name AS group_name,
      COALESCE((SELECT COUNT(*) FROM host_group_members hgm WHERE hgm.host_group_id = hg.id), 0) AS member_count,
      COALESCE((SELECT json_group_array(host_id) FROM host_group_members hgm WHERE hgm.host_group_id = hg.id), '[]') AS host_ids
    FROM host_groups hg
    LEFT JOIN groups g ON g.id = hg.group_id
    WHERE hg.id = ? AND ${visibleClause('hg')}
  `).get(groupId, userId, userId);
  return row ? camelHostGroup(row) : null;
}

export function createHostGroup(body, userId) {
  const payload = normalizeHostGroup(body);
  if (payload.mode === 'dynamic' && !payload.rules.length) throw new Error('Dynamic host groups require at least one rule.');
  const groupId = id('hgrp');
  db.prepare(`
    INSERT INTO host_groups (
      id, name, description, mode, match_mode, rules, visibility, owner_user_id,
      group_id, created_by, created_at, updated_at
    )
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    groupId,
    payload.name,
    payload.description,
    payload.mode,
    payload.matchMode,
    json(payload.rules),
    payload.visibility,
    userId,
    payload.groupId,
    userId,
    now(),
    now()
  );
  if (payload.mode === 'manual') replaceHostGroupMembers(groupId, filterVisibleHostIds(payload.hostIds, userId), 'manual');
  else syncHostGroup(groupId);
  return getVisibleHostGroup(groupId, userId);
}

export function updateHostGroup(groupId, body, userId) {
  const existing = db.prepare(`SELECT * FROM host_groups WHERE id = ? AND ${visibleClause()}`).get(groupId, userId, userId);
  if (!existing) return null;
  const payload = normalizeHostGroup({
    name: existing.name,
    description: existing.description || '',
    mode: existing.mode,
    matchMode: existing.match_mode,
    rules: parseJson(existing.rules, []),
    visibility: existing.visibility,
    groupId: existing.group_id,
    ...body
  });
  if (payload.mode === 'dynamic' && !payload.rules.length) throw new Error('Dynamic host groups require at least one rule.');
  db.prepare(`
    UPDATE host_groups
    SET name = ?, description = ?, mode = ?, match_mode = ?, rules = ?,
        visibility = ?, group_id = ?, updated_at = ?
    WHERE id = ?
  `).run(
    payload.name,
    payload.description,
    payload.mode,
    payload.matchMode,
    json(payload.rules),
    payload.visibility,
    payload.groupId,
    now(),
    groupId
  );
  if (payload.mode === 'manual') replaceHostGroupMembers(groupId, filterVisibleHostIds(payload.hostIds, userId), 'manual');
  else syncHostGroup(groupId);
  return getVisibleHostGroup(groupId, userId);
}

export function deleteHostGroup(groupId, userId) {
  db.prepare(`DELETE FROM host_groups WHERE id = ? AND ${visibleClause()}`).run(groupId, userId, userId);
}

export function filterVisibleHostGroupIds(groupIds = [], userId) {
  if (!groupIds.length) return [];
  const visible = new Set(
    db.prepare(`SELECT id FROM host_groups WHERE ${visibleClause()}`).all(userId, userId).map((row) => row.id)
  );
  return groupIds.filter((groupId) => visible.has(groupId));
}

export function syncHostGroup(groupId) {
  const group = db.prepare('SELECT * FROM host_groups WHERE id = ?').get(groupId);
  if (!group || group.mode !== 'dynamic') return { groupId, matched: 0, skipped: true };
  const ownerId = group.owner_user_id || group.created_by;
  const hosts = ownerId
    ? db.prepare(`SELECT * FROM hosts WHERE ${visibleClause()} ORDER BY name`).all(ownerId, ownerId)
    : db.prepare("SELECT * FROM hosts WHERE visibility = 'shared' ORDER BY name").all();
  const rules = parseJson(group.rules, []);
  const matched = hosts.filter((host) => hostMatchesRules(host, rules, group.match_mode)).map((host) => host.id);
  replaceHostGroupMembers(groupId, matched, 'rule');
  db.prepare('UPDATE host_groups SET last_synced_at = ?, updated_at = ? WHERE id = ?').run(now(), now(), groupId);
  return { groupId, matched: matched.length, skipped: false };
}

export function syncDynamicHostGroups() {
  const groups = db.prepare("SELECT id FROM host_groups WHERE mode = 'dynamic'").all();
  const summary = { evaluated: 0, synced: 0, matched: 0, errors: 0 };
  for (const group of groups) {
    summary.evaluated += 1;
    try {
      const result = syncHostGroup(group.id);
      if (!result.skipped) {
        summary.synced += 1;
        summary.matched += result.matched;
      }
    } catch {
      summary.errors += 1;
    }
  }
  return summary;
}

function replaceHostGroupMembers(groupId, hostIds, source) {
  db.prepare('DELETE FROM host_group_members WHERE host_group_id = ?').run(groupId);
  const stmt = db.prepare(`
    INSERT OR IGNORE INTO host_group_members (host_group_id, host_id, source, created_at)
    VALUES (?, ?, ?, ?)
  `);
  for (const hostId of [...new Set(hostIds)]) stmt.run(groupId, hostId, source, now());
}

export function hostMatchesRules(host, rules = [], matchMode = 'all') {
  if (!rules.length) return false;
  const results = rules.map((rule) => hostMatchesRule(host, rule));
  return matchMode === 'any' ? results.some(Boolean) : results.every(Boolean);
}

function hostMatchesRule(host, rule) {
  const values = hostRuleValues(host, rule.field);
  const needle = String(rule.value || '').toLowerCase();
  return values.some((value) => compareRuleValue(String(value || '').toLowerCase(), needle, rule.operator));
}

function hostRuleValues(host, field) {
  if (field === 'tags') return parseJson(host.tags, []);
  if (field === 'sourceType') return [host.source_type || 'manual'];
  if (field === 'osFamily') return [host.os_family || ''];
  return [host[field] || ''];
}

function compareRuleValue(value, needle, operator = 'contains') {
  if (operator === 'equals') return value === needle;
  if (operator === 'startsWith') return value.startsWith(needle);
  if (operator === 'endsWith') return value.endsWith(needle);
  return value.includes(needle);
}
