import { getDb, queryOne, queryAll, execute, saveDb } from '../db/database';
import type { ConnectionTokenResponse, TokenVerifyResponse, ConnectionLog } from '@network-tool/shared-types';
import { generateConnectionToken, sha256, generateNonce } from '@network-tool/shared-crypto';
import { JWT_SECRET } from './auth.service';

export class ConnectionService {
  async requestConnection(userId: number, data: {
    device_id: number;
    node_id: number;
  }): Promise<ConnectionTokenResponse> {
    const db = await getDb();

    const device = queryOne(db, 'SELECT * FROM devices WHERE id = ? AND user_id = ?', [data.device_id, userId]);
    if (!device) throw new Error('DEVICE_NOT_FOUND');

    const node = queryOne(db, "SELECT * FROM relay_nodes WHERE id = ? AND status = 'online'", [data.node_id]);
    if (!node) throw new Error('NODE_NOT_AVAILABLE');

    execute(db, "UPDATE devices SET status = 'online', last_seen_at = datetime('now') WHERE id = ?", [data.device_id]);

    const nonce = generateNonce();
    const token = generateConnectionToken({
      user_id: userId,
      device_id: data.device_id,
      node_id: data.node_id,
      nonce,
    }, JWT_SECRET, '5m');

    const tokenHash = sha256(token);
    const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString();

    execute(db,
      'INSERT INTO connection_tokens (user_id, device_id, node_id, token_hash, expires_at) VALUES (?, ?, ?, ?, ?)',
      [userId, data.device_id, data.node_id, tokenHash, expiresAt]
    );

    this._log(db, userId, data.device_id, data.node_id, 'token_issued', 'success', 'Token issued');

    saveDb();

    return {
      token,
      node: node as any,
      expires_at: expiresAt,
    };
  }

  async verifyToken(data: { token: string; device_id: number }): Promise<TokenVerifyResponse> {
    const db = await getDb();

    const tokenHash = sha256(data.token);
    const record = queryOne<any>(db,
      `SELECT * FROM connection_tokens
       WHERE token_hash = ? AND device_id = ? AND used_at IS NULL
       AND expires_at > datetime('now')`,
      [tokenHash, data.device_id]
    );

    if (!record) {
      this._log(db, null, data.device_id, null, 'verify_token', 'failed', 'Invalid or expired token');
      return { valid: false, user_id: 0, device_id: 0, message: 'Invalid or expired token' };
    }

    execute(db, "UPDATE connection_tokens SET used_at = datetime('now') WHERE id = ?", [record.id]);
    execute(db, "UPDATE devices SET status = 'online', last_seen_at = datetime('now') WHERE id = ?", [data.device_id]);
    this._log(db, record.user_id, record.device_id, record.node_id, 'verify_token', 'success', 'Token verified');
    saveDb();

    return { valid: true, user_id: record.user_id, device_id: record.device_id, message: 'Token verified' };
  }

  async disconnect(userId: number, deviceId: number): Promise<void> {
    const db = await getDb();
    execute(db, "UPDATE devices SET status = 'offline', last_seen_at = datetime('now') WHERE id = ? AND user_id = ?",
      [deviceId, userId]);
    this._log(db, userId, deviceId, null, 'disconnect', 'success', 'Disconnected');
    saveDb();
  }

  async getLogs(limit = 100): Promise<ConnectionLog[]> {
    const db = await getDb();
    return queryAll<ConnectionLog>(db, 'SELECT * FROM connection_logs ORDER BY created_at DESC LIMIT ?', [limit]);
  }

  async getUserLogs(userId: number, limit = 50): Promise<ConnectionLog[]> {
    const db = await getDb();
    return queryAll<ConnectionLog>(db,
      'SELECT * FROM connection_logs WHERE user_id = ? ORDER BY created_at DESC LIMIT ?',
      [userId, limit]
    );
  }

  async cleanupExpiredTokens(): Promise<void> {
    const db = await getDb();
    execute(db, "DELETE FROM connection_tokens WHERE expires_at < datetime('now') AND used_at IS NULL");
    saveDb();
  }

  private _log(db: any, userId: number | null, deviceId: number | null, nodeId: number | null,
    action: string, status: 'success' | 'failed', message: string): void {
    execute(db,
      'INSERT INTO connection_logs (user_id, device_id, node_id, action, status, message) VALUES (?, ?, ?, ?, ?, ?)',
      [userId, deviceId, nodeId, action, status, message]
    );
  }
}
