/**
 * @fileoverview Base Database Service for Platform Services
 * @author Matthew Raymer
 *
 * This abstract base class provides common database operations that are
 * identical across all platform implementations. It eliminates code
 * duplication and ensures consistency in database operations.
 *
 * Key Features:
 * - Common database utility methods
 * - Consistent settings management
 * - Active identity management
 * - Abstract methods for platform-specific database operations
 *
 * Architecture:
 * - Abstract base class with common implementations
 * - Platform services extend this class
 * - Platform-specific database operations remain abstract
 *
 * @since 1.1.1-beta
 */

import { logger } from "../../utils/logger";
import { QueryExecResult } from "@/interfaces/database";

/**
 * Abstract base class for platform-specific database services.
 *
 * This class provides common database operations that are identical
 * across all platform implementations (Web, Capacitor, Electron).
 * Platform-specific services extend this class and implement the
 * abstract database operation methods.
 *
 * Common Operations:
 * - Settings management (update, retrieve, insert)
 * - Active identity management
 * - Database utility methods
 *
 * @abstract
 * @example
 * ```typescript
 * export class WebPlatformService extends BaseDatabaseService {
 *   async dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
 *     // Web-specific implementation
 *   }
 * }
 * ```
 */
export abstract class BaseDatabaseService {
  /**
   * Generate an INSERT statement for a model object.
   *
   * Creates a parameterized INSERT statement with placeholders for
   * all properties in the model object. This ensures safe SQL
   * execution and prevents SQL injection.
   *
   * @param model - Object containing the data to insert
   * @param tableName - Name of the target table
   * @returns Object containing the SQL statement and parameters
   *
   * @example
   * ```typescript
   * const { sql, params } = this.generateInsertStatement(
   *   { name: 'John', age: 30 },
   *   'users'
   * );
   * // sql: "INSERT INTO users (name, age) VALUES (?, ?)"
   * // params: ['John', 30]
   * ```
   */
  generateInsertStatement(
    model: Record<string, unknown>,
    tableName: string,
  ): { sql: string; params: unknown[] } {
    const keys = Object.keys(model);
    const placeholders = keys.map(() => "?").join(", ");
    const sql = `INSERT INTO ${tableName} (${keys.join(", ")}) VALUES (${placeholders})`;
    const params = keys.map((key) => model[key]);
    return { sql, params };
  }

  /**
   * Update default settings for the currently active account.
   *
   * Retrieves the active DID from the active_identity table and updates
   * the corresponding settings record. This ensures settings are always
   * updated for the correct account.
   *
   * @param settings - Object containing the settings to update
   * @returns Promise that resolves when settings are updated
   *
   * @throws {Error} If no active DID is found or database operation fails
   *
   * @example
   * ```typescript
   * await this.updateDefaultSettings({
   *   theme: 'dark',
   *   notifications: true
   * });
   * ```
   */
  async updateDefaultSettings(
    settings: Record<string, unknown>,
  ): Promise<void> {
    // Get current active DID and update that identity's settings
    const activeIdentity = await this.getActiveIdentity();
    const activeDid = activeIdentity.activeDid;

    if (!activeDid) {
      logger.warn(
        "[BaseDatabaseService] No active DID found, cannot update default settings",
      );
      return;
    }

    const keys = Object.keys(settings);
    const setClause = keys.map((key) => `${key} = ?`).join(", ");
    const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
    const params = [...keys.map((key) => settings[key]), activeDid];
    await this.dbExec(sql, params);
  }

  /**
   * Update the active DID in the active_identity table.
   *
   * Sets the active DID and updates the lastUpdated timestamp.
   * This is used when switching between different accounts/identities.
   *
   * @param did - The DID to set as active
   * @returns Promise that resolves when the update is complete
   *
   * @example
   * ```typescript
   * await this.updateActiveDid('did:example:123');
   * ```
   */
  async updateActiveDid(did: string): Promise<void> {
    await this.dbExec(
      "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
      [did],
    );
  }

  /**
   * Get the currently active DID from the active_identity table.
   *
   * Retrieves the active DID that represents the currently selected
   * account/identity. This is used throughout the application to
   * ensure operations are performed on the correct account.
   *
   * @returns Promise resolving to object containing the active DID
   *
   * @example
   * ```typescript
   * const { activeDid } = await this.getActiveIdentity();
   * console.log('Current active DID:', activeDid);
   * ```
   */
  async getActiveIdentity(): Promise<{ activeDid: string }> {
    const result = (await this.dbQuery(
      "SELECT activeDid FROM active_identity WHERE id = 1",
    )) as QueryExecResult;
    return {
      activeDid: (result?.values?.[0]?.[0] as string) || "",
    };
  }

  /**
   * Insert a new DID into the settings table with default values.
   *
   * Creates a new settings record for a DID with default configuration
   * values. Uses INSERT OR REPLACE to handle cases where settings
   * already exist for the DID.
   *
   * @param did - The DID to create settings for
   * @returns Promise that resolves when settings are created
   *
   * @example
   * ```typescript
   * await this.insertNewDidIntoSettings('did:example:123');
   * ```
   */
  async insertNewDidIntoSettings(did: string): Promise<void> {
    // Import constants dynamically to avoid circular dependencies
    const { DEFAULT_ENDORSER_API_SERVER, DEFAULT_PARTNER_API_SERVER } =
      await import("@/constants/app");

    // Use INSERT OR REPLACE to handle case where settings already exist for this DID
    // This prevents duplicate accountDid entries and ensures data integrity
    await this.dbExec(
      "INSERT OR REPLACE INTO settings (accountDid, finishedOnboarding, apiServer, partnerApiServer) VALUES (?, ?, ?, ?)",
      [did, false, DEFAULT_ENDORSER_API_SERVER, DEFAULT_PARTNER_API_SERVER],
    );
  }

  /**
   * Update settings for a specific DID.
   *
   * Updates settings for a particular DID rather than the active one.
   * This is useful for bulk operations or when managing multiple accounts.
   *
   * @param did - The DID to update settings for
   * @param settings - Object containing the settings to update
   * @returns Promise that resolves when settings are updated
   *
   * @example
   * ```typescript
   * await this.updateDidSpecificSettings('did:example:123', {
   *   theme: 'light',
   *   notifications: false
   * });
   * ```
   */
  async updateDidSpecificSettings(
    did: string,
    settings: Record<string, unknown>,
  ): Promise<void> {
    const keys = Object.keys(settings);
    const setClause = keys.map((key) => `${key} = ?`).join(", ");
    const sql = `UPDATE settings SET ${setClause} WHERE accountDid = ?`;
    const params = [...keys.map((key) => settings[key]), did];
    await this.dbExec(sql, params);
  }

  /**
   * Retrieve settings for the currently active account.
   *
   * Gets the active DID and retrieves all settings for that account.
   * Excludes the 'id' column from the returned settings object.
   *
   * @returns Promise resolving to settings object or null if no active DID
   *
   * @example
   * ```typescript
   * const settings = await this.retrieveSettingsForActiveAccount();
   * if (settings) {
   *   console.log('Theme:', settings.theme);
   *   console.log('Notifications:', settings.notifications);
   * }
   * ```
   */
  async retrieveSettingsForActiveAccount(): Promise<Record<
    string,
    unknown
  > | null> {
    // Get current active DID from active_identity table
    const activeIdentity = await this.getActiveIdentity();
    const activeDid = activeIdentity.activeDid;

    if (!activeDid) {
      return null;
    }

    const result = (await this.dbQuery(
      "SELECT * FROM settings WHERE accountDid = ?",
      [activeDid],
    )) as QueryExecResult;
    if (result?.values?.[0]) {
      // Convert the row to an object
      const row = result.values[0];
      const columns = result.columns || [];
      const settings: Record<string, unknown> = {};

      columns.forEach((column: string, index: number) => {
        if (column !== "id") {
          // Exclude the id column
          settings[column] = row[index];
        }
      });

      return settings;
    }
    return null;
  }

  // Abstract methods that must be implemented by platform-specific services

  /**
   * Execute a database query (SELECT operations).
   *
   * @abstract
   * @param sql - SQL query string
   * @param params - Optional parameters for prepared statements
   * @returns Promise resolving to query results
   */
  abstract dbQuery(sql: string, params?: unknown[]): Promise<unknown>;

  /**
   * Execute a database statement (INSERT, UPDATE, DELETE operations).
   *
   * @abstract
   * @param sql - SQL statement string
   * @param params - Optional parameters for prepared statements
   * @returns Promise resolving to execution results
   */
  abstract dbExec(sql: string, params?: unknown[]): Promise<unknown>;
}
