import sqlite3 from "sqlite3";
import { EventEmitter } from "events";
import {
  IDatabaseAdapter,
  IUrl,
  SqliteConfig,
} from "../interfaces/database.interface";

export class SqliteAdapter extends EventEmitter implements IDatabaseAdapter {
  private db: sqlite3.Database | null = null;
  private config: SqliteConfig;
  private tableName: string;
  private retryCount: number = 0;
  private _isConnected: boolean = false;

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

  async connect(): Promise<boolean> {
    try {
      if (this._isConnected && this.db) {
        return true;
      }

      return new Promise((resolve, reject) => {
        this.db = new sqlite3.Database(
          this.config.database,
          (err: Error | null) => {
            if (err) {
              console.error("SQLite connection error:", err);
              this.emit("error", err);
              reject(err);
              return;
            }

            this._isConnected = true;
            this.retryCount = 0;
            this.emit("connected");

            // Create tables if they don't exist
            this.createTables()
              .then(() => resolve(true))
              .catch(reject);
          }
        );

        // Handle connection events
        this.db!.on("error", (err: Error) => {
          console.error("SQLite database error:", err);
          this._isConnected = false;
          this.emit("error", err);
          this.retryConnection();
        });
      });
    } catch (error) {
      console.error("Failed to connect to SQLite:", error);
      this.emit("error", error);
      return this.retryConnection();
    }
  }

  private async createTables(): Promise<void> {
    if (!this.db) 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 INTEGER PRIMARY KEY AUTOINCREMENT,
        urlId TEXT UNIQUE NOT NULL,
        originalUrl TEXT NOT NULL,
        shortUrl TEXT NOT NULL,
        clicks INTEGER DEFAULT 0,
        createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
        expiresAt DATETIME NULL
      )
    `;

    return new Promise((resolve, reject) => {
      this.db!.run(createTableSQL, (err: Error | null) => {
        if (err) {
          reject(err);
        } else {
          // Create indexes for performance
          this.db!.run(
            `CREATE INDEX IF NOT EXISTS idx_${this.tableName}_urlId ON ${quotedTableName}(urlId)`,
            (err: Error | null) => {
              if (err) console.warn("Failed to create urlId index:", err);
            }
          );
          this.db!.run(
            `CREATE INDEX IF NOT EXISTS idx_${this.tableName}_originalUrl ON ${quotedTableName}(originalUrl)`,
            (err: Error | null) => {
              if (err) console.warn("Failed to create originalUrl index:", err);
            }
          );
          resolve();
        }
      });
    });
  }

  private retryConnection(): boolean {
    if (this.retryCount < this.config.maxRetries!) {
      this.retryCount++;
      const delay = this.config.retryDelay! * Math.pow(2, this.retryCount - 1);
      console.log(
        `Retrying SQLite connection in ${delay}ms (attempt ${this.retryCount}/${this.config.maxRetries})`
      );

      setTimeout(() => {
        this.connect();
      }, delay);
      return true;
    } else {
      this.emit("maxRetriesReached");
      return false;
    }
  }

  async disconnect(): Promise<void> {
    if (this.db) {
      return new Promise((resolve) => {
        this.db!.close((err: Error | null) => {
          if (err) console.error("Error closing SQLite connection:", err);
          this.db = null;
          this._isConnected = false;
          this.emit("disconnected");
          resolve();
        });
      });
    }
  }

  isConnected(): boolean {
    return this._isConnected && this.db !== null;
  }

  async createUrl(url: Omit<IUrl, "createdAt">): Promise<IUrl> {
    if (!this.db) 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 (?, ?, ?, ?, ?)
    `;

    return new Promise((resolve, reject) => {
      this.db!.run(
        sql,
        [
          url.urlId,
          url.originalUrl,
          url.shortUrl,
          url.clicks,
          url.expiresAt ? url.expiresAt.toISOString() : null,
        ],
        function (err) {
          if (err) {
            reject(err);
          } else {
            resolve({
              ...url,
              createdAt: new Date(),
            });
          }
        }
      );
    });
  }

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

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

    const sql = `SELECT * FROM ${quotedTableName} WHERE urlId = ?`;

    return new Promise((resolve, reject) => {
      this.db!.get(sql, [urlId], (err: Error | null, row: any) => {
        if (err) {
          reject(err);
        } else if (!row) {
          resolve(null);
        } else {
          resolve({
            urlId: row.urlId,
            originalUrl: row.originalUrl,
            shortUrl: row.shortUrl,
            clicks: row.clicks,
            createdAt: new Date(row.createdAt),
            expiresAt: row.expiresAt ? new Date(row.expiresAt) : undefined,
          });
        }
      });
    });
  }

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

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

    const sql = `SELECT * FROM ${quotedTableName} WHERE originalUrl = ?`;

    return new Promise((resolve, reject) => {
      this.db!.get(sql, [originalUrl], (err: Error | null, row: any) => {
        if (err) {
          reject(err);
        } else if (!row) {
          resolve(null);
        } else {
          resolve({
            urlId: row.urlId,
            originalUrl: row.originalUrl,
            shortUrl: row.shortUrl,
            clicks: row.clicks,
            createdAt: new Date(row.createdAt),
            expiresAt: row.expiresAt ? new Date(row.expiresAt) : undefined,
          });
        }
      });
    });
  }

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

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

    const sql = `UPDATE ${quotedTableName} SET clicks = ? WHERE urlId = ?`;

    return new Promise((resolve, reject) => {
      this.db!.run(sql, [clicks, urlId], (err) => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });
  }

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

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

    const sql = `DELETE FROM ${quotedTableName} WHERE urlId = ?`;

    return new Promise((resolve, reject) => {
      this.db!.run(sql, [urlId], function (err) {
        if (err) {
          reject(err);
        } else {
          resolve(this.changes > 0);
        }
      });
    });
  }
}
