import { Injectable, inject } from '@angular/core';
import {
  DELETED_MESSAGE_CONTENT,
  type BanEntry,
  type Message,
  type Reaction,
  type Room,
  type User
} from '../../shared-kernel';
import type {
  ChatAttachmentMeta,
  CustomEmoji,
  MessageRevision
} from '../../shared-kernel';
import { getStoredCurrentUserId } from '../../core/storage/current-user-storage';
import {
  attachmentToValues,
  banToValues,
  customEmojiToValues,
  messageToRow,
  reactionToValues,
  roomToRow,
  rowToAttachment,
  rowToCustomEmoji,
  rowToMessage,
  rowToRoom,
  rowToUser,
  userToRow,
  type MessageRow,
  type RoomRow,
  type UserRow
} from '../mobile/logic/mobile-sqlite-row-mapper.rules';
import { MobileSqliteConnectionService } from '../mobile/services/mobile-sqlite-connection.service';
import type { RoomMessageStats } from './database.service';

/**
 * SQLite-backed database service for Capacitor native shells.
 *
 * Mirrors the {@link BrowserDatabaseService} API using `@capacitor-community/sqlite`.
 */
@Injectable({ providedIn: 'root' })
export class CapacitorDatabaseService {
  private readonly connection = inject(MobileSqliteConnectionService);

  async initialize(): Promise<void> {
    await this.connection.initialize();
  }

  async saveMessageRevision(revision: MessageRevision): Promise<void> {
    const store = await this.connection.getStore();

    await store.run(
      'INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)',
      [`message-revision:${revision.messageId}:${revision.revision}`, JSON.stringify(revision)]
    );
  }

  async saveMessage(message: Message): Promise<void> {
    const store = await this.connection.getStore();
    const row = messageToRow(message);

    await store.run(
      `INSERT OR REPLACE INTO messages (
        id, roomId, ownerUserId, channelId, senderId, senderName, content,
        timestamp, editedAt, isDeleted, replyToId, linkMetadata, kind, systemEvent,
        revision, headHash
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      [
        row.id,
        row.roomId,
        row.ownerUserId ?? null,
        row.channelId ?? null,
        row.senderId,
        row.senderName,
        row.content,
        row.timestamp,
        row.editedAt ?? null,
        row.isDeleted,
        row.replyToId ?? null,
        row.linkMetadata ?? null,
        row.kind ?? null,
        row.systemEvent ?? null,
        row.revision ?? 0,
        row.headHash ?? null
      ]
    );
  }

  async getMessages(
    roomId: string,
    limit = 100,
    offset = 0,
    channelId?: string,
    beforeTimestamp?: number
  ): Promise<Message[]> {
    const store = await this.connection.getStore();
    const rows = await store.query<MessageRow>(`
      SELECT * FROM messages
      WHERE roomId = ?
      ${beforeTimestamp !== undefined ? 'AND timestamp < ?' : ''}
      ORDER BY timestamp ASC
    `, beforeTimestamp !== undefined ? [roomId, beforeTimestamp] : [roomId]);
    const scopedRows = channelId
      ? rows.filter((row) => (row.channelId || 'general') === channelId)
      : rows;
    const endIndex = Math.max(scopedRows.length - offset, 0);
    const startIndex = Math.max(endIndex - limit, 0);
    const slice = scopedRows.slice(startIndex, endIndex);

    return this.hydrateMessages(slice.map((row) => rowToMessage(row)));
  }

  async getMessagesSince(roomId: string, sinceTimestamp: number): Promise<Message[]> {
    const store = await this.connection.getStore();
    const rows = await store.query<MessageRow>(
      'SELECT * FROM messages WHERE roomId = ? AND timestamp > ? ORDER BY timestamp ASC',
      [roomId, sinceTimestamp]
    );

    return this.hydrateMessages(rows.map((row) => rowToMessage(row)));
  }

  async getRoomMessageStats(roomId: string): Promise<RoomMessageStats> {
    const store = await this.connection.getStore();
    const rows = await store.query<{ count: number; lastUpdated: number }>(
      `SELECT COUNT(*) as count, MAX(COALESCE(editedAt, timestamp, 0)) as lastUpdated
       FROM messages WHERE roomId = ?`,
      [roomId]
    );

    return {
      count: Number(rows[0]?.count ?? 0),
      lastUpdated: Number(rows[0]?.lastUpdated ?? 0)
    };
  }

  async deleteMessage(messageId: string): Promise<void> {
    const store = await this.connection.getStore();

    await store.run('DELETE FROM messages WHERE id = ?', [messageId]);
  }

  async updateMessage(messageId: string, updates: Partial<Message>): Promise<void> {
    const existing = await this.getMessageById(messageId);

    if (existing) {
      await this.saveMessage({ ...existing, ...updates });
    }
  }

  async getMessageById(messageId: string): Promise<Message | null> {
    const store = await this.connection.getStore();
    const rows = await store.query<MessageRow>('SELECT * FROM messages WHERE id = ? LIMIT 1', [messageId]);
    const row = rows[0];

    if (!row) {
      return null;
    }

    const messages = await this.hydrateMessages([rowToMessage(row)]);

    return messages[0] ?? null;
  }

  async clearRoomMessages(roomId: string): Promise<void> {
    const store = await this.connection.getStore();

    await store.run('DELETE FROM messages WHERE roomId = ?', [roomId]);
  }

  async saveReaction(reaction: Reaction): Promise<void> {
    const store = await this.connection.getStore();
    const existing = await store.query<Reaction>(
      'SELECT * FROM reactions WHERE messageId = ? AND userId = ? AND emoji = ? LIMIT 1',
      [
        reaction.messageId,
        reaction.userId,
        reaction.emoji
      ]
    );

    if (existing.length === 0) {
      await store.run(
        'INSERT OR REPLACE INTO reactions (id, messageId, oderId, userId, emoji, timestamp) VALUES (?, ?, ?, ?, ?, ?)',
        reactionToValues(reaction)
      );
    }
  }

  async removeReaction(messageId: string, userId: string, emoji: string): Promise<void> {
    const store = await this.connection.getStore();

    await store.run(
      'DELETE FROM reactions WHERE messageId = ? AND userId = ? AND emoji = ?',
      [
        messageId,
        userId,
        emoji
      ]
    );
  }

  async getReactionsForMessage(messageId: string): Promise<Reaction[]> {
    const store = await this.connection.getStore();

    return store.query<Reaction>(
      'SELECT * FROM reactions WHERE messageId = ? ORDER BY timestamp ASC',
      [messageId]
    );
  }

  async saveUser(user: User): Promise<void> {
    const store = await this.connection.getStore();
    const row = userToRow(user);

    await store.run(
      `INSERT OR REPLACE INTO users (
        id, oderId, username, displayName, description, profileUpdatedAt,
        avatarUrl, avatarHash, avatarMime, avatarUpdatedAt, status, role,
        joinedAt, peerId, isOnline, isAdmin, isRoomOwner, voiceState,
        screenShareState, homeSignalServerUrl
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      [
        row.id,
        row.oderId ?? null,
        row.username ?? null,
        row.displayName ?? null,
        row.description ?? null,
        row.profileUpdatedAt ?? null,
        row.avatarUrl ?? null,
        row.avatarHash ?? null,
        row.avatarMime ?? null,
        row.avatarUpdatedAt ?? null,
        row.status ?? null,
        row.role ?? null,
        row.joinedAt ?? null,
        row.peerId ?? null,
        row.isOnline,
        row.isAdmin,
        row.isRoomOwner,
        row.voiceState ?? null,
        row.screenShareState ?? null,
        row.homeSignalServerUrl ?? null
      ]
    );
  }

  async getUser(userId: string): Promise<User | null> {
    const store = await this.connection.getStore();
    const rows = await store.query<UserRow>('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);

    return rows[0] ? rowToUser(rows[0]) : null;
  }

  async getCurrentUser(): Promise<User | null> {
    const userId = await this.getCurrentUserId();

    return userId ? this.getUser(userId) : null;
  }

  async getCurrentUserId(): Promise<string | null> {
    const store = await this.connection.getStore();
    const rows = await store.query<{ value: string }>(
      "SELECT value FROM meta WHERE key = 'currentUserId' LIMIT 1"
    );

    return rows[0]?.value?.trim() || null;
  }

  async setCurrentUserId(userId: string): Promise<void> {
    const store = await this.connection.getStore();

    await store.run(
      "INSERT OR REPLACE INTO meta (key, value) VALUES ('currentUserId', ?)",
      [userId]
    );

    if (getStoredCurrentUserId() !== userId) {
      await this.connection.initialize();
    }
  }

  async getUsersByRoom(_roomId: string): Promise<User[]> {
    const store = await this.connection.getStore();
    const rows = await store.query<UserRow>('SELECT * FROM users');

    return rows.map(rowToUser);
  }

  async updateUser(userId: string, updates: Partial<User>): Promise<void> {
    const existing = await this.getUser(userId);

    if (existing) {
      await this.saveUser({ ...existing, ...updates });
    }
  }

  async saveRoom(room: Room): Promise<void> {
    const store = await this.connection.getStore();
    const row = roomToRow(room);

    await store.run(
      `INSERT OR REPLACE INTO rooms (
        id, name, description, topic, hostId, password, hasPassword, isPrivate,
        createdAt, userCount, maxUsers, icon, iconUpdatedAt, slowModeInterval,
        sourceId, sourceName, sourceUrl
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      [
        row.id,
        row.name,
        row.description ?? null,
        row.topic ?? null,
        row.hostId,
        row.password ?? null,
        row.hasPassword,
        row.isPrivate,
        row.createdAt,
        row.userCount,
        row.maxUsers ?? null,
        row.icon ?? null,
        row.iconUpdatedAt ?? null,
        row.slowModeInterval,
        row.sourceId ?? null,
        row.sourceName ?? null,
        row.sourceUrl ?? null
      ]
    );
  }

  async getRoom(roomId: string): Promise<Room | null> {
    const store = await this.connection.getStore();
    const rows = await store.query<RoomRow>('SELECT * FROM rooms WHERE id = ? LIMIT 1', [roomId]);

    return rows[0] ? rowToRoom(rows[0]) : null;
  }

  async getAllRooms(): Promise<Room[]> {
    const store = await this.connection.getStore();
    const rows = await store.query<RoomRow>('SELECT * FROM rooms ORDER BY createdAt ASC');

    return rows.map(rowToRoom);
  }

  async deleteRoom(roomId: string): Promise<void> {
    const store = await this.connection.getStore();

    await store.run('DELETE FROM rooms WHERE id = ?', [roomId]);
    await this.clearRoomMessages(roomId);
  }

  async updateRoom(roomId: string, updates: Partial<Room>): Promise<void> {
    const existing = await this.getRoom(roomId);

    if (existing) {
      await this.saveRoom({ ...existing, ...updates });
    }
  }

  async saveBan(ban: BanEntry): Promise<void> {
    const store = await this.connection.getStore();

    await store.run(
      `INSERT OR REPLACE INTO bans (
        oderId, roomId, userId, bannedBy, displayName, reason, expiresAt, timestamp
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
      banToValues(ban)
    );
  }

  async removeBan(oderId: string): Promise<void> {
    const store = await this.connection.getStore();

    await store.run('DELETE FROM bans WHERE oderId = ?', [oderId]);
  }

  async getBansForRoom(roomId: string): Promise<BanEntry[]> {
    const store = await this.connection.getStore();
    const now = Date.now();
    const rows = await store.query<BanEntry>(
      'SELECT * FROM bans WHERE roomId = ?',
      [roomId]
    );

    return rows.filter((ban) => !ban.expiresAt || ban.expiresAt > now);
  }

  async isUserBanned(userId: string, roomId: string): Promise<boolean> {
    const activeBans = await this.getBansForRoom(roomId);

    return activeBans.some((ban) => ban.oderId === userId);
  }

  async saveAttachment(attachment: ChatAttachmentMeta): Promise<void> {
    const store = await this.connection.getStore();

    await store.run(
      `INSERT OR REPLACE INTO attachments (
        id, messageId, filename, size, mime, isImage, uploaderPeerId, filePath, savedPath
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      attachmentToValues(attachment)
    );
  }

  async getAttachmentsForMessage(messageId: string): Promise<ChatAttachmentMeta[]> {
    const store = await this.connection.getStore();
    const rows = await store.query<Parameters<typeof rowToAttachment>[0]>(
      'SELECT * FROM attachments WHERE messageId = ?',
      [messageId]
    );

    return rows.map(rowToAttachment);
  }

  async getAllAttachments(): Promise<ChatAttachmentMeta[]> {
    const store = await this.connection.getStore();
    const rows = await store.query<Parameters<typeof rowToAttachment>[0]>('SELECT * FROM attachments');

    return rows.map(rowToAttachment);
  }

  async saveCustomEmoji(emoji: CustomEmoji): Promise<void> {
    const store = await this.connection.getStore();

    await store.run(
      `INSERT OR REPLACE INTO custom_emojis (
        id, name, creatorUserId, dataUrl, hash, mime, size, createdAt, updatedAt
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      customEmojiToValues(emoji)
    );
  }

  async getCustomEmojis(): Promise<CustomEmoji[]> {
    const store = await this.connection.getStore();
    const rows = await store.query<Parameters<typeof rowToCustomEmoji>[0]>('SELECT * FROM custom_emojis');

    return rows.map(rowToCustomEmoji);
  }

  async deleteCustomEmoji(emojiId: string): Promise<void> {
    const store = await this.connection.getStore();

    await store.run('DELETE FROM custom_emojis WHERE id = ?', [emojiId]);
  }

  async deleteAttachmentsForMessage(messageId: string): Promise<void> {
    const store = await this.connection.getStore();

    await store.run('DELETE FROM attachments WHERE messageId = ?', [messageId]);
  }

  async clearAllData(): Promise<void> {
    const store = await this.connection.getStore();
    const tables = [
      'messages',
      'users',
      'rooms',
      'reactions',
      'bans',
      'attachments',
      'custom_emojis',
      'meta',
      'push_device_tokens'
    ];

    for (const table of tables) {
      await store.run(`DELETE FROM ${table}`);
    }
  }

  private async hydrateMessages(messages: Message[]): Promise<Message[]> {
    if (messages.length === 0) {
      return [];
    }

    const reactionsByMessageId = await this.loadReactionsForMessages(messages.map((message) => message.id));

    return messages.map((message) => this.normaliseMessage({
      ...message,
      reactions: reactionsByMessageId.get(message.id) ?? message.reactions ?? []
    }));
  }

  private async loadReactionsForMessages(messageIds: readonly string[]): Promise<Map<string, Reaction[]>> {
    const messageIdSet = new Set(messageIds.filter((messageId) => messageId.trim().length > 0));
    const reactionsByMessageId = new Map<string, Reaction[]>();

    if (messageIdSet.size === 0) {
      return reactionsByMessageId;
    }

    const store = await this.connection.getStore();
    const allReactions = await store.query<Reaction>('SELECT * FROM reactions');

    for (const reaction of allReactions) {
      if (!messageIdSet.has(reaction.messageId)) {
        continue;
      }

      const reactions = reactionsByMessageId.get(reaction.messageId) ?? [];

      reactions.push(reaction);
      reactionsByMessageId.set(reaction.messageId, reactions);
    }

    for (const reactions of reactionsByMessageId.values()) {
      reactions.sort((first, second) => first.timestamp - second.timestamp);
    }

    return reactionsByMessageId;
  }

  private normaliseMessage(message: Message): Message {
    if (message.content === DELETED_MESSAGE_CONTENT) {
      return { ...message, reactions: [] };
    }

    return message;
  }
}
