import mysql from "mysql2/promise";
import { EventEmitter } from "events";
import {
  IDatabaseAdapter,
  IUrl,
  MysqlConfig,
} from "../interfaces/database.interface";

export class MysqlAdapter extends EventEmitter implements IDatabaseAdapter {
  private pool: mysql.Pool | null = null;
  private config: MysqlConfig;
  private tableName: string;
  private isConnectedFlag: boolean = false;

  constructor(config: MysqlConfig) {
    super();
    this.config = {
      maxRetries: 5,
      retryDelay: 5000,
      ...config,
    };
    this.tableName = config.tableName || "urls";
  }

  async connect(): Promise<boolean> {
    try {
      if (this.isConnectedFlag && this.pool) {
        return true;
      }
      this.pool = mysql.createPool({
        host: this.config.host,
        port: this.config.port,
        user: this.config.username,
        password: this.config.password,
        database: this.config.database,
        waitForConnections: true,
        connectionLimit: 10,
        queueLimit: 0,
      });
      await this.createTables();
      this.isConnectedFlag = true;
      this.emit("connected");
      return true;
    } catch (error) {
      this.emit("error", error);
      return false;
    }
  }

  async createTables(): Promise<void> {
    if (!this.pool) throw new Error("Database not connected");

    // Quote table name to preserve case
    const quotedTableName = `\`${this.tableName}\``;

    const createTableSQL = `
      CREATE TABLE IF NOT EXISTS ${quotedTableName} (
        id INT AUTO_INCREMENT PRIMARY KEY,
        urlId VARCHAR(255) UNIQUE NOT NULL,
        originalUrl TEXT NOT NULL,
        shortUrl TEXT NOT NULL,
        clicks INT DEFAULT 0,
        createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
        expiresAt DATETIME NULL
      );
    `;
    // MySQL does not support CREATE INDEX IF NOT EXISTS, so we need to check manually
    const createIndex1 = `CREATE INDEX idx_${this.tableName}_urlId ON ${quotedTableName}(urlId)`;
    const createIndex2 = `CREATE INDEX idx_${this.tableName}_originalUrl ON ${quotedTableName}(originalUrl)`;
    await this.pool.query(createTableSQL);

    const [rows] = await this.pool.query(
      `SHOW INDEX FROM ${quotedTableName} WHERE Key_name = "idx_${this.tableName}_urlId"`
    );
    if ((rows as any[]).length === 0) {
      await this.pool.query(createIndex1);
    }
    const [rows2] = await this.pool.query(
      `SHOW INDEX FROM ${quotedTableName} WHERE Key_name = "idx_${this.tableName}_originalUrl"`
    );
    if ((rows2 as any[]).length === 0) {
      await this.pool.query(createIndex2);
    }
  }

  async disconnect(): Promise<void> {
    if (this.pool) {
      await this.pool.end();
      this.pool = null;
      this.isConnectedFlag = false;
      this.emit("disconnected");
    }
  }

  isConnected(): boolean {
    return this.isConnectedFlag && this.pool !== null;
  }

  async createUrl(url: Omit<IUrl, "createdAt">): Promise<IUrl> {
    if (!this.pool) throw new Error("Database not connected");

    // Quote table name to preserve case
    const quotedTableName = `\`${this.tableName}\``;

    const sql = `INSERT INTO ${quotedTableName} (urlId, originalUrl, shortUrl, clicks, expiresAt) VALUES (?, ?, ?, ?, ?)`;
    const [result] = await this.pool.query(sql, [
      url.urlId,
      url.originalUrl,
      url.shortUrl,
      url.clicks,
      url.expiresAt
        ? url.expiresAt.toISOString().slice(0, 19).replace("T", " ")
        : null,
    ]);
    // MySQL does not return the row, so fetch it
    return (await this.findUrlByUrlId(url.urlId))!;
  }

  async findUrlByUrlId(urlId: string): Promise<IUrl | null> {
    if (!this.pool) throw new Error("Database not connected");

    // Quote table name to preserve case
    const quotedTableName = `\`${this.tableName}\``;

    const sql = `SELECT * FROM ${quotedTableName} WHERE urlId = ? LIMIT 1`;
    const [rows] = await this.pool.query(sql, [urlId]);
    const row = (rows as any[])[0];
    if (!row) return null;
    return {
      urlId: row.urlId,
      originalUrl: row.originalUrl,
      shortUrl: row.shortUrl,
      clicks: row.clicks,
      createdAt: row.createdAt,
      expiresAt: row.expiresAt ? new Date(row.expiresAt) : undefined,
    };
  }

  async findUrlByOriginalUrl(originalUrl: string): Promise<IUrl | null> {
    if (!this.pool) throw new Error("Database not connected");

    // Quote table name to preserve case
    const quotedTableName = `\`${this.tableName}\``;

    const sql = `SELECT * FROM ${quotedTableName} WHERE originalUrl = ? LIMIT 1`;
    const [rows] = await this.pool.query(sql, [originalUrl]);
    const row = (rows as any[])[0];
    if (!row) return null;
    return {
      urlId: row.urlId,
      originalUrl: row.originalUrl,
      shortUrl: row.shortUrl,
      clicks: row.clicks,
      createdAt: row.createdAt,
      expiresAt: row.expiresAt ? new Date(row.expiresAt) : undefined,
    };
  }

  async updateUrlClicks(urlId: string, clicks: number): Promise<void> {
    if (!this.pool) throw new Error("Database not connected");

    // Quote table name to preserve case
    const quotedTableName = `\`${this.tableName}\``;

    const sql = `UPDATE ${quotedTableName} SET clicks = ? WHERE urlId = ?`;
    await this.pool.query(sql, [clicks, urlId]);
  }

  async deleteUrl(urlId: string): Promise<boolean> {
    if (!this.pool) throw new Error("Database not connected");

    // Quote table name to preserve case
    const quotedTableName = `\`${this.tableName}\``;

    const sql = `DELETE FROM ${quotedTableName} WHERE urlId = ?`;
    const [result]: any = await this.pool.query(sql, [urlId]);
    return result.affectedRows > 0;
  }
}
