import { IUrl, IDatabaseAdapter } from "../interfaces/database.interface";
import { URL } from "url";
import { generateId } from "../utils/id-generator";

export interface ShortifyOptions {
  baseUrl: string;
  urlLength?: number;
  customUrlId?: string;
  expiresInDays?: number;
}

export interface ShortifyResult {
  originalUrl: string;
  shortUrl: string;
  urlId: string;
  expiresAt?: Date;
}

export class ShortenerService {
  private defaultUrlLength: number = 8;
  private baseUrl: string;
  private dbAdapter: IDatabaseAdapter;

  constructor(baseUrl: string, dbAdapter: IDatabaseAdapter) {
    // Ensure the base URL ends with a slash
    this.baseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
    this.dbAdapter = dbAdapter;
  }

  /**
   * Validate if a URL is properly formatted
   */
  private isValidUrl(urlString: string): boolean {
    try {
      const url = new URL(urlString);
      return url.protocol === "http:" || url.protocol === "https:";
    } catch (error) {
      return false;
    }
  }

  /**
   * Generate a short URL ID
   */
  private generateUrlId(length: number = this.defaultUrlLength): string {
    return generateId(length);
  }

  /**
   * Check if a URL ID already exists in the database
   * For performance, we use this to avoid duplicate URL IDs
   */
  private async findExistingUrlId(urlId: string): Promise<IUrl | null> {
    return this.dbAdapter.findUrlByUrlId(urlId);
  }

  /**
   * Shorten a URL
   */
  async shorten(
    originalUrl: string,
    options: Partial<ShortifyOptions> = {}
  ): Promise<ShortifyResult> {
    // Validate the original URL
    if (!this.isValidUrl(originalUrl)) {
      throw new Error("Invalid URL format");
    }

    // Apply options with defaults
    const urlLength = options.urlLength || this.defaultUrlLength;
    const baseUrl = options.baseUrl || this.baseUrl;

    // Generate URL ID (either custom or generated)
    let urlId = options.customUrlId || this.generateUrlId(urlLength);

    // Check if the URL ID already exists and generate a new one if needed
    let existingUrl = await this.findExistingUrlId(urlId);
    while (existingUrl) {
      urlId = this.generateUrlId(urlLength);
      existingUrl = await this.findExistingUrlId(urlId);
    }

    // Calculate expiration date if provided
    let expiresAt: Date | undefined = undefined;
    if (options.expiresInDays) {
      expiresAt = new Date();
      expiresAt.setDate(expiresAt.getDate() + options.expiresInDays);
    }

    // Create the short URL
    const shortUrl = `${baseUrl}${urlId}`;

    // Save the URL to database
    await this.dbAdapter.createUrl({
      urlId,
      originalUrl,
      shortUrl,
      clicks: 0,
      expiresAt,
    });

    return {
      originalUrl,
      shortUrl,
      urlId,
      expiresAt,
    };
  }

  /**
   * Resolve a short URL to its original URL
   */
  async resolve(urlId: string): Promise<string | null> {
    const url = await this.dbAdapter.findUrlByUrlId(urlId);

    if (!url) {
      return null;
    }

    // Check if URL has expired
    if (url.expiresAt && url.expiresAt < new Date()) {
      // URL has expired, delete it
      await this.dbAdapter.deleteUrl(urlId);
      return null;
    }

    // Increment click count
    await this.dbAdapter.updateUrlClicks(urlId, url.clicks + 1);

    return url.originalUrl;
  }

  /**
   * Get URL stats
   */
  async getUrlStats(urlId: string): Promise<Partial<IUrl> | null> {
    const url = await this.dbAdapter.findUrlByUrlId(urlId);

    if (!url) {
      return null;
    }

    return {
      urlId: url.urlId,
      originalUrl: url.originalUrl,
      shortUrl: url.shortUrl,
      clicks: url.clicks,
      createdAt: url.createdAt,
      expiresAt: url.expiresAt,
    };
  }

  /**
   * Delete a shortened URL
   */
  async deleteUrl(urlId: string): Promise<boolean> {
    return this.dbAdapter.deleteUrl(urlId);
  }
}
