import { db, id, json, now, parseJson, visibleClause } from '../db.js';
import { graphConnectionSchema } from '../forms/schemas.js';
import { camelGraphConnection, camelIntuneDeploymentStatus, camelIntuneDeploymentSyncRun } from './mappers.js';
import { decryptSecret, encryptSecret } from '../services/cryptoStore.js';

function normalizeConnection(body, existing = {}) {
  const parsed = graphConnectionSchema.parse({ ...existing, ...body });
  const cloudDefaults = cloudEndpointDefaults(parsed.cloud);
  return {
    ...parsed,
    graphBaseUrl: body.graphBaseUrl || existing.graphBaseUrl || cloudDefaults.graphBaseUrl,
    authorityHost: body.authorityHost || existing.authorityHost || cloudDefaults.authorityHost,
    enabled: Boolean(parsed.enabled),
    allowWrite: Boolean(parsed.allowWrite),
    requireApproval: Boolean(parsed.requireApproval),
    publisherIds: Array.isArray(parsed.publisherIds) ? parsed.publisherIds : [],
    approverIds: Array.isArray(parsed.approverIds) ? parsed.approverIds : [],
    groupId: parsed.visibility === 'group' ? parsed.groupId || null : null
  };
}

export function cloudEndpointDefaults(cloud = 'global') {
  const endpoints = {
    global: { graphBaseUrl: 'https://graph.microsoft.com', authorityHost: 'https://login.microsoftonline.com' },
    gcc: { graphBaseUrl: 'https://graph.microsoft.com', authorityHost: 'https://login.microsoftonline.com' },
    gcchigh: { graphBaseUrl: 'https://graph.microsoft.us', authorityHost: 'https://login.microsoftonline.us' },
    dod: { graphBaseUrl: 'https://dod-graph.microsoft.us', authorityHost: 'https://login.microsoftonline.us' },
    china: { graphBaseUrl: 'https://microsoftgraph.chinacloudapi.cn', authorityHost: 'https://login.chinacloudapi.cn' }
  };
  return endpoints[cloud] || endpoints.global;
}

export function listGraphConnections(userId) {
  return db.prepare(`
    SELECT c.*, g.name AS group_name
    FROM graph_connections c
    LEFT JOIN groups g ON g.id = c.group_id
    WHERE ${visibleClause('c')}
    ORDER BY c.name
  `).all(userId, userId).map(camelGraphConnection);
}

export function getGraphConnection(connectionId, userId, includeSecret = false) {
  const row = db.prepare(`
    SELECT c.*, g.name AS group_name
    FROM graph_connections c
    LEFT JOIN groups g ON g.id = c.group_id
    WHERE c.id = ? AND ${visibleClause('c')}
  `).get(connectionId, userId, userId);
  if (!row) return null;
  return includeSecret ? { ...row, clientSecret: decryptSecret(row) } : camelGraphConnection(row);
}

export function createGraphConnection(body, userId) {
  const payload = normalizeConnection(body);
  const encrypted = encryptSecret(payload.clientSecret);
  const connectionId = id('graph');
  db.prepare(`
    INSERT INTO graph_connections (
      id, name, tenant_id, client_id, secret_cipher, secret_iv, secret_tag, cloud,
      graph_base_url, authority_host, default_api_version, enabled, allow_write, require_approval,
      publisher_ids, approver_ids, visibility, owner_user_id, group_id, created_by, created_at, updated_at
    )
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    connectionId,
    payload.name,
    payload.tenantId,
    payload.clientId,
    encrypted.cipher,
    encrypted.iv,
    encrypted.tag,
    payload.cloud,
    payload.graphBaseUrl,
    payload.authorityHost,
    payload.defaultApiVersion,
    payload.enabled ? 1 : 0,
    payload.allowWrite ? 1 : 0,
    payload.requireApproval ? 1 : 0,
    json(payload.publisherIds, []),
    json(payload.approverIds, []),
    payload.visibility,
    userId,
    payload.groupId,
    userId,
    now(),
    now()
  );
  return getGraphConnection(connectionId, userId);
}

export function updateGraphConnection(connectionId, body, userId) {
  const existing = getGraphConnection(connectionId, userId, true);
  if (!existing) return null;
  const payload = normalizeConnection(body, {
    name: existing.name,
    tenantId: existing.tenant_id,
    clientId: existing.client_id,
    cloud: existing.cloud,
    graphBaseUrl: existing.graph_base_url,
    authorityHost: existing.authority_host,
    defaultApiVersion: existing.default_api_version,
    enabled: Boolean(existing.enabled),
    allowWrite: Boolean(existing.allow_write),
    requireApproval: Boolean(existing.require_approval),
    publisherIds: parseJson(existing.publisher_ids, []),
    approverIds: parseJson(existing.approver_ids, []),
    visibility: existing.visibility,
    groupId: existing.group_id
  });
  const encrypted = payload.clientSecret ? encryptSecret(payload.clientSecret) : {
    cipher: existing.secret_cipher,
    iv: existing.secret_iv,
    tag: existing.secret_tag
  };
  db.prepare(`
    UPDATE graph_connections
    SET name = ?, tenant_id = ?, client_id = ?, secret_cipher = ?, secret_iv = ?, secret_tag = ?,
        cloud = ?, graph_base_url = ?, authority_host = ?, default_api_version = ?, enabled = ?, allow_write = ?, require_approval = ?,
        publisher_ids = ?, approver_ids = ?, visibility = ?, group_id = ?, updated_at = ?
    WHERE id = ?
  `).run(
    payload.name,
    payload.tenantId,
    payload.clientId,
    encrypted.cipher,
    encrypted.iv,
    encrypted.tag,
    payload.cloud,
    payload.graphBaseUrl,
    payload.authorityHost,
    payload.defaultApiVersion,
    payload.enabled ? 1 : 0,
    payload.allowWrite ? 1 : 0,
    payload.requireApproval ? 1 : 0,
    json(payload.publisherIds, []),
    json(payload.approverIds, []),
    payload.visibility,
    payload.groupId,
    now(),
    connectionId
  );
  return getGraphConnection(connectionId, userId);
}

export function deleteGraphConnection(connectionId, userId) {
  db.prepare(`DELETE FROM graph_connections WHERE id = ? AND ${visibleClause()}`).run(connectionId, userId, userId);
}

// Immutable record of every tenant-changing Graph action (publish, assign,
// relationship edits). Stores the actor, request payload, and Graph response so
// changes are auditable and replayable.
export function recordGraphAudit(entry) {
  const auditId = id('gaud');
  db.prepare(`
    INSERT INTO graph_audit_log (
      id, connection_id, deployment_id, actor_user_id, actor_email, action,
      target_graph_id, status, message, request_json, response_json, created_at
    )
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `).run(
    auditId,
    entry.connectionId || null,
    entry.deploymentId || null,
    entry.actorUserId || null,
    entry.actorEmail || '',
    entry.action,
    entry.targetGraphId || '',
    entry.status,
    entry.message || '',
    json(entry.request || {}, {}),
    json(entry.response || {}, {}),
    now()
  );
  return auditId;
}

export function listGraphAudit({ deploymentId, connectionId, limit = 100 } = {}) {
  const clauses = [];
  const params = [];
  if (deploymentId) { clauses.push('deployment_id = ?'); params.push(deploymentId); }
  if (connectionId) { clauses.push('connection_id = ?'); params.push(connectionId); }
  const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
  return db.prepare(`
    SELECT * FROM graph_audit_log ${where} ORDER BY created_at DESC LIMIT ?
  `).all(...params, Number(limit) || 100).map((row) => ({
    id: row.id,
    connectionId: row.connection_id,
    deploymentId: row.deployment_id,
    actorUserId: row.actor_user_id,
    actorEmail: row.actor_email || '',
    action: row.action,
    targetGraphId: row.target_graph_id || '',
    status: row.status,
    message: row.message || '',
    request: parseJson(row.request_json, {}),
    response: parseJson(row.response_json, {}),
    createdAt: row.created_at
  }));
}

// Read just the governance lists for a connection, without visibility scoping,
// so designated approvers (who may not "own" the connection) can be authorized.
// Full connection row + decrypted secret without visibility scope, for the
// background auto-promote worker (system context).
export function getGraphConnectionSystem(connectionId) {
  const row = db.prepare('SELECT * FROM graph_connections WHERE id = ?').get(connectionId);
  if (!row) return null;
  return { ...row, clientSecret: decryptSecret(row) };
}

export function getConnectionGovernance(connectionId) {
  const row = db.prepare('SELECT publisher_ids, approver_ids, require_approval FROM graph_connections WHERE id = ?').get(connectionId);
  if (!row) return null;
  return {
    publisherIds: parseJson(row.publisher_ids, []),
    approverIds: parseJson(row.approver_ids, []),
    requireApproval: Boolean(row.require_approval)
  };
}

export function recordGraphConnectionTest(connectionId, status, message) {
  db.prepare(`
    UPDATE graph_connections
    SET last_test_status = ?, last_test_message = ?, last_test_at = ?, updated_at = ?
    WHERE id = ?
  `).run(status, message, now(), now(), connectionId);
}

export function upsertDeploymentStatuses(deploymentId, connectionId, graphAppId, rows = []) {
  const timestamp = now();
  const stmt = db.prepare(`
    INSERT INTO intune_deployment_statuses (
      id, deployment_id, connection_id, graph_app_id, scope, principal_id, principal_name,
      device_id, device_name, user_id, user_principal_name, install_state, install_state_detail,
      error_code, error_description, last_sync_datetime, raw_json, created_at, updated_at
    )
    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `);
  db.prepare('DELETE FROM intune_deployment_statuses WHERE deployment_id = ? AND connection_id = ? AND graph_app_id = ?')
    .run(deploymentId, connectionId, graphAppId);
  for (const row of rows) {
    stmt.run(
      id('ids'),
      deploymentId,
      connectionId,
      graphAppId,
      row.scope || 'device',
      row.principalId || '',
      row.principalName || '',
      row.deviceId || '',
      row.deviceName || '',
      row.userId || '',
      row.userPrincipalName || '',
      row.installState || 'unknown',
      row.installStateDetail || '',
      row.errorCode == null ? '' : String(row.errorCode),
      row.errorDescription || '',
      row.lastSyncDateTime || '',
      json(row.raw || {}, {}),
      timestamp,
      timestamp
    );
  }
}

export function createSyncRun(deploymentId, connectionId, graphAppId) {
  const runId = id('sync');
  db.prepare(`
    INSERT INTO intune_deployment_sync_runs (id, deployment_id, connection_id, graph_app_id, status, started_at, summary)
    VALUES (?, ?, ?, ?, 'running', ?, '{}')
  `).run(runId, deploymentId, connectionId, graphAppId, now());
  return runId;
}

export function finishSyncRun(runId, status, message, summary = {}) {
  db.prepare(`
    UPDATE intune_deployment_sync_runs
    SET status = ?, message = ?, summary = ?, ended_at = ?
    WHERE id = ?
  `).run(status, message || '', json(summary, {}), now(), runId);
}

export function listDeploymentStatuses(deploymentId) {
  return db.prepare(`
    SELECT * FROM intune_deployment_statuses
    WHERE deployment_id = ?
    ORDER BY
      CASE install_state WHEN 'failed' THEN 0 WHEN 'error' THEN 1 WHEN 'pending' THEN 2 WHEN 'installed' THEN 3 ELSE 4 END,
      updated_at DESC
  `).all(deploymentId).map(camelIntuneDeploymentStatus);
}

// Cross-deployment rollout health for the governance/reporting view. Scoped to
// the deployments the operator can see (admins see all).
export function intuneReportingOverview(userId, isAdmin = false) {
  const scope = isAdmin ? '1=1' : visibleClause('d');
  const params = isAdmin ? [] : [userId, userId];
  const deploymentsByStatus = db.prepare(`
    SELECT status, COUNT(*) AS count FROM intune_deployments d WHERE ${scope} GROUP BY status
  `).all(...params);
  const installStateCounts = db.prepare(`
    SELECT s.install_state AS state, COUNT(*) AS count
    FROM intune_deployment_statuses s
    JOIN intune_deployments d ON d.id = s.deployment_id
    WHERE ${scope}
    GROUP BY s.install_state
  `).all(...params);
  const topErrors = db.prepare(`
    SELECT s.error_code AS code, COUNT(*) AS count
    FROM intune_deployment_statuses s
    JOIN intune_deployments d ON d.id = s.deployment_id
    WHERE ${scope} AND s.error_code IS NOT NULL AND s.error_code <> ''
    GROUP BY s.error_code
    ORDER BY count DESC
    LIMIT 10
  `).all(...params);
  const writeActions = db.prepare(`
    SELECT a.action, a.status, COUNT(*) AS count
    FROM graph_audit_log a
    JOIN intune_deployments d ON d.id = a.deployment_id
    WHERE ${scope}
    GROUP BY a.action, a.status
  `).all(...params);

  // SLA / soak timers: deployments mid-soak, with hours remaining until the
  // auto-promote window elapses (negative = overdue for promotion).
  const soakTimers = db.prepare(`
    SELECT d.id, d.name, d.auto_promote_ring AS ring, d.auto_promote_after_hours AS hours, d.soak_started_at AS soakStartedAt,
      ROUND(d.auto_promote_after_hours - (julianday('now') - julianday(d.soak_started_at)) * 24, 1) AS hoursRemaining
    FROM intune_deployments d
    WHERE d.auto_promote_after_hours > 0 AND d.soak_started_at IS NOT NULL AND d.soak_started_at <> ''
      AND (d.promoted_at IS NULL OR d.promoted_at = '') AND ${scope}
    ORDER BY hoursRemaining ASC
  `).all(...params);

  // Write-action trend over the last 14 days, by day and outcome.
  const auditTrend = db.prepare(`
    SELECT substr(a.created_at, 1, 10) AS day, a.status, COUNT(*) AS count
    FROM graph_audit_log a
    JOIN intune_deployments d ON d.id = a.deployment_id
    WHERE ${scope} AND a.created_at >= date('now', '-14 days')
    GROUP BY day, a.status
    ORDER BY day
  `).all(...params);

  return { deploymentsByStatus, installStateCounts, topErrors, writeActions, soakTimers, auditTrend };
}

export function listDeploymentSyncRuns(deploymentId) {
  return db.prepare(`
    SELECT * FROM intune_deployment_sync_runs
    WHERE deployment_id = ?
    ORDER BY started_at DESC
    LIMIT 20
  `).all(deploymentId).map(camelIntuneDeploymentSyncRun);
}
