/* eslint-disable @typescript-eslint/member-ordering, */
import {
  inject,
  Injectable,
  signal
} from '@angular/core';
import {
  Message,
  User,
  Room,
  Reaction,
  BanEntry,
  type MessageRevision
} from '../../shared-kernel';
import type { ChatAttachmentMeta, CustomEmoji } from '../../shared-kernel';
import { PlatformService } from '../../core/platform';
import { getStoredCurrentUserId } from '../../core/storage/current-user-storage';
import { BrowserDatabaseService } from './browser-database.service';
import { CapacitorDatabaseService } from './capacitor-database.service';
import { resolveDatabaseBackend } from './database-backend.rules';
import { ElectronDatabaseService } from './electron-database.service';

export interface RoomMessageStats {
  count: number;
  lastUpdated: number;
}

/**
 * Facade database service that transparently delegates to the correct
 * storage backend based on the runtime platform.
 *
 * - Electron -> SQLite via {@link ElectronDatabaseService} (IPC to main process).
 * - Capacitor -> native SQLite via {@link CapacitorDatabaseService}.
 * - Browser -> IndexedDB via {@link BrowserDatabaseService}.
 *
 * All consumers inject `DatabaseService`; the underlying storage engine
 * is selected automatically.
 */
@Injectable({ providedIn: 'root' })
export class DatabaseService {
  private readonly platform = inject(PlatformService);
  private readonly browserDb = inject(BrowserDatabaseService);
  private readonly capacitorDb = inject(CapacitorDatabaseService);
  private readonly electronDb = inject(ElectronDatabaseService);
  private initializationPromise: Promise<void> | null = null;
  private validatedUserScope: string | null | undefined;

  /** Reactive flag: `true` once {@link initialize} has completed. */
  isReady = signal(false);

  /** The active storage backend for the current platform. */
  private get backend() {
    const backendKind = resolveDatabaseBackend({
      isElectron: this.platform.isElectron,
      isCapacitor: this.platform.isCapacitor
    });

    if (backendKind === 'electron') {
      return this.electronDb;
    }

    if (backendKind === 'capacitor-sqlite') {
      return this.capacitorDb;
    }

    return this.browserDb;
  }

  /** Initialise the platform-specific database. */
  async initialize(): Promise<void> {
    const userScope = getStoredCurrentUserId();

    if (this.initializationPromise) {
      await this.initializationPromise;

      if (this.isReady() && this.validatedUserScope === userScope) {
        return;
      }
    } else if (this.isReady() && this.validatedUserScope === userScope) {
      return;
    }

    const backend = this.backend;

    this.initializationPromise = backend.initialize()
      .then(() => {
        this.validatedUserScope = userScope;
        this.isReady.set(true);
      })
      .finally(() => {
        this.initializationPromise = null;
      });

    await this.initializationPromise;
  }

  private async ensureReady(): Promise<void> {
    const userScope = getStoredCurrentUserId();

    if (this.isReady() && this.validatedUserScope === userScope) {
      return;
    }

    await this.initialize();
  }

  private async withReady<T>(operation: () => Promise<T>): Promise<T> {
    await this.ensureReady();

    return operation();
  }

  /** Persist a single chat message. */
  saveMessage(message: Message) { return this.withReady(() => this.backend.saveMessage(message)); }

  /** Persist an append-only message revision audit entry. */
  saveMessageRevision(revision: MessageRevision) {
    return this.withReady(() => this.backend.saveMessageRevision(revision));
  }

  /** Retrieve the latest messages for a room or channel with optional pagination.
   *
   * When `beforeTimestamp` is provided, only messages strictly older than that
   * timestamp are returned. This is how scroll-up history loading paginates
   * backwards through the DB without holding the whole history in memory.
   */
  getMessages(
    roomId: string,
    limit = 100,
    offset = 0,
    channelId?: string,
    beforeTimestamp?: number
  ) { return this.withReady(() => this.backend.getMessages(roomId, limit, offset, channelId, beforeTimestamp)); }

  /** Retrieve messages newer than a given timestamp for a room. */
  getMessagesSince(roomId: string, sinceTimestamp: number) { return this.withReady(() => this.backend.getMessagesSince(roomId, sinceTimestamp)); }

  /** Retrieve aggregate message stats for sync handshakes without loading history. */
  getRoomMessageStats(roomId: string) { return this.withReady(() => this.backend.getRoomMessageStats(roomId)); }

  /** Permanently delete a message by ID. */
  deleteMessage(messageId: string) { return this.withReady(() => this.backend.deleteMessage(messageId)); }

  /** Apply partial updates to an existing message. */
  updateMessage(messageId: string, updates: Partial<Message>) { return this.withReady(() => this.backend.updateMessage(messageId, updates)); }

  /** Retrieve a single message by ID. */
  getMessageById(messageId: string) { return this.withReady(() => this.backend.getMessageById(messageId)); }

  /** Remove every message belonging to a room. */
  clearRoomMessages(roomId: string) { return this.withReady(() => this.backend.clearRoomMessages(roomId)); }

  /** Persist a reaction. */
  saveReaction(reaction: Reaction) { return this.withReady(() => this.backend.saveReaction(reaction)); }

  /** Remove a specific reaction (user + emoji + message). */
  removeReaction(messageId: string, userId: string, emoji: string) {
    return this.withReady(() => this.backend.removeReaction(messageId, userId, emoji));
  }

  /** Return all reactions for a given message. */
  getReactionsForMessage(messageId: string) { return this.withReady(() => this.backend.getReactionsForMessage(messageId)); }

  /** Persist a user record. */
  saveUser(user: User) { return this.withReady(() => this.backend.saveUser(user)); }

  /** Retrieve a user by ID. */
  getUser(userId: string) { return this.withReady(() => this.backend.getUser(userId)); }

  /** Retrieve the current (logged-in) user. */
  getCurrentUser() { return this.withReady(() => this.backend.getCurrentUser()); }

  /** Retrieve the persisted current user ID without loading the full user. */
  getCurrentUserId() { return this.withReady(() => this.backend.getCurrentUserId()); }

  /** Store the current user ID. */
  setCurrentUserId(userId: string) { return this.withReady(() => this.backend.setCurrentUserId(userId)); }

  /** Retrieve users in a room. */
  getUsersByRoom(roomId: string) { return this.withReady(() => this.backend.getUsersByRoom(roomId)); }

  /** Apply partial updates to an existing user. */
  updateUser(userId: string, updates: Partial<User>) { return this.withReady(() => this.backend.updateUser(userId, updates)); }

  /** Persist a room record. */
  saveRoom(room: Room) { return this.withReady(() => this.backend.saveRoom(room)); }

  /** Retrieve a room by ID. */
  getRoom(roomId: string) { return this.withReady(() => this.backend.getRoom(roomId)); }

  /** Return every persisted room. */
  getAllRooms() { return this.withReady(() => this.backend.getAllRooms()); }

  /** Delete a room and its associated messages. */
  deleteRoom(roomId: string) { return this.withReady(() => this.backend.deleteRoom(roomId)); }

  /** Apply partial updates to an existing room. */
  updateRoom(roomId: string, updates: Partial<Room>) { return this.withReady(() => this.backend.updateRoom(roomId, updates)); }

  /** Persist a ban entry. */
  saveBan(ban: BanEntry) { return this.withReady(() => this.backend.saveBan(ban)); }

  /** Remove a ban by oderId. */
  removeBan(oderId: string) { return this.withReady(() => this.backend.removeBan(oderId)); }

  /** Return active bans for a room. */
  getBansForRoom(roomId: string) { return this.withReady(() => this.backend.getBansForRoom(roomId)); }

  /** Check whether a user is currently banned from a room. */
  isUserBanned(userId: string, roomId: string) { return this.withReady(() => this.backend.isUserBanned(userId, roomId)); }

  /** Persist attachment metadata. */
  saveAttachment(attachment: ChatAttachmentMeta) { return this.withReady(() => this.backend.saveAttachment(attachment)); }

  /** Return all attachment records for a message. */
  getAttachmentsForMessage(messageId: string) { return this.withReady(() => this.backend.getAttachmentsForMessage(messageId)); }

  /** Return every persisted attachment record. */
  getAllAttachments() { return this.withReady(() => this.backend.getAllAttachments()); }

  /** Persist a custom emoji asset. */
  saveCustomEmoji(emoji: CustomEmoji) { return this.withReady(() => this.backend.saveCustomEmoji(emoji)); }

  /** Return every known custom emoji asset. */
  getCustomEmojis() { return this.withReady(() => this.backend.getCustomEmojis()); }

  /** Delete a custom emoji asset. */
  deleteCustomEmoji(emojiId: string) { return this.withReady(() => this.backend.deleteCustomEmoji(emojiId)); }

  /** Delete all attachment records for a message. */
  deleteAttachmentsForMessage(messageId: string) { return this.withReady(() => this.backend.deleteAttachmentsForMessage(messageId)); }

  /** Wipe all persisted data. */
  clearAllData() { return this.withReady(() => this.backend.clearAllData()); }
}
