export type ParseResult = {
  host: string;
  port: number;
  username: string;
  password: string;
  database: string;
  isHttps: boolean;
};

export type ParseError = {
  error: string;
  format?: string;
};

export class MariadbConnectionStringParser {
  /**
   * Parses a MariaDB connection string in various formats.
   *
   * Supported formats:
   * 1. Standard MariaDB URI: mariadb://user:pass@host:port/db
   * 2. MySQL URI (compatible): mysql://user:pass@host:port/db
   * 3. JDBC format: jdbc:mariadb://host:port/db?user=x&password=y
   * 4. JDBC MySQL format: jdbc:mysql://host:port/db?user=x&password=y
   * 5. Key-value format: host=x port=3306 database=db user=u password=p
   * 6. With SSL params: mariadb://user:pass@host:port/db?ssl=true or ?sslMode=REQUIRED
   * 7. SkySQL: mariadb://user:pass@xxx.skysql.net:5001/db?ssl=true
   */
  static parse(connectionString: string): ParseResult | ParseError {
    const trimmed = connectionString.trim();

    if (!trimmed) {
      return { error: 'Connection string is empty' };
    }

    // Try JDBC format first (starts with jdbc:)
    if (trimmed.startsWith('jdbc:mariadb://') || trimmed.startsWith('jdbc:mysql://')) {
      return this.parseJdbc(trimmed);
    }

    // Try key-value format (contains key=value pairs without ://)
    if (this.isKeyValueFormat(trimmed)) {
      return this.parseKeyValue(trimmed);
    }

    // Try URI format (mariadb:// or mysql://)
    if (trimmed.startsWith('mariadb://') || trimmed.startsWith('mysql://')) {
      return this.parseUri(trimmed);
    }

    return {
      error: 'Unrecognized connection string format',
    };
  }

  private static isKeyValueFormat(str: string): boolean {
    return (
      !str.includes('://') &&
      (str.includes('host=') || str.includes('database=')) &&
      str.includes('=')
    );
  }

  private static parseUri(connectionString: string): ParseResult | ParseError {
    try {
      // Handle Azure format where username contains @: user@server:pass
      const azureMatch = connectionString.match(
        /^(?:mariadb|mysql):\/\/([^@:]+)@([^:]+):([^@]+)@([^:/?]+):?(\d+)?\/([^?]+)(?:\?(.*))?$/,
      );

      if (azureMatch) {
        const [, user, , password, host, port, database, queryString] = azureMatch;
        const isHttps = this.checkSslMode(queryString);

        return {
          host: host,
          port: port ? parseInt(port, 10) : 3306,
          username: decodeURIComponent(user),
          password: decodeURIComponent(password),
          database: decodeURIComponent(database),
          isHttps,
        };
      }

      // Standard URI parsing using URL API
      const url = new URL(connectionString);

      const host = url.hostname;
      const port = url.port ? parseInt(url.port, 10) : 3306;
      const username = decodeURIComponent(url.username);
      const password = decodeURIComponent(url.password);
      const database = decodeURIComponent(url.pathname.slice(1));
      const isHttps = this.checkSslMode(url.search);

      if (!host) {
        return { error: 'Host is missing from connection string' };
      }

      if (!username) {
        return { error: 'Username is missing from connection string' };
      }

      if (!password) {
        return { error: 'Password is missing from connection string' };
      }

      if (!database) {
        return { error: 'Database name is missing from connection string' };
      }

      return {
        host,
        port,
        username,
        password,
        database,
        isHttps,
      };
    } catch (e) {
      return {
        error: `Failed to parse connection string: ${(e as Error).message}`,
        format: 'URI',
      };
    }
  }

  private static parseJdbc(connectionString: string): ParseResult | ParseError {
    try {
      const jdbcRegex = /^jdbc:(?:mariadb|mysql):\/\/([^:/?]+):?(\d+)?\/([^?]+)(?:\?(.*))?$/;
      const match = connectionString.match(jdbcRegex);

      if (!match) {
        return {
          error:
            'Invalid JDBC connection string format. Expected: jdbc:mariadb://host:port/database?user=x&password=y',
          format: 'JDBC',
        };
      }

      const [, host, port, database, queryString] = match;

      if (!queryString) {
        return {
          error: 'JDBC connection string is missing query parameters (user and password)',
          format: 'JDBC',
        };
      }

      const params = new URLSearchParams(queryString);
      const username = params.get('user');
      const password = params.get('password');
      const isHttps = this.checkSslMode(queryString);

      if (!username) {
        return {
          error: 'Username (user parameter) is missing from JDBC connection string',
          format: 'JDBC',
        };
      }

      if (!password) {
        return {
          error: 'Password parameter is missing from JDBC connection string',
          format: 'JDBC',
        };
      }

      return {
        host,
        port: port ? parseInt(port, 10) : 3306,
        username: decodeURIComponent(username),
        password: decodeURIComponent(password),
        database: decodeURIComponent(database),
        isHttps,
      };
    } catch (e) {
      return {
        error: `Failed to parse JDBC connection string: ${(e as Error).message}`,
        format: 'JDBC',
      };
    }
  }

  private static parseKeyValue(connectionString: string): ParseResult | ParseError {
    try {
      const params: Record<string, string> = {};

      const regex = /(\w+)=(?:'([^']*)'|(\S+))/g;
      let match;

      while ((match = regex.exec(connectionString)) !== null) {
        const key = match[1];
        const value = match[2] !== undefined ? match[2] : match[3];
        params[key] = value;
      }

      const host = params['host'] || params['hostaddr'];
      const port = params['port'];
      const database = params['database'] || params['dbname'];
      const username = params['user'] || params['username'];
      const password = params['password'];
      const ssl = params['ssl'] || params['sslMode'] || params['ssl-mode'] || params['useSSL'];

      if (!host) {
        return {
          error: 'Host is missing from connection string. Use host=hostname',
          format: 'key-value',
        };
      }

      if (!username) {
        return {
          error: 'Username is missing from connection string. Use user=username',
          format: 'key-value',
        };
      }

      if (!password) {
        return {
          error: 'Password is missing from connection string. Use password=yourpassword',
          format: 'key-value',
        };
      }

      if (!database) {
        return {
          error: 'Database name is missing from connection string. Use database=database',
          format: 'key-value',
        };
      }

      const isHttps = this.isSslEnabled(ssl);

      return {
        host,
        port: port ? parseInt(port, 10) : 3306,
        username,
        password,
        database,
        isHttps,
      };
    } catch (e) {
      return {
        error: `Failed to parse key-value connection string: ${(e as Error).message}`,
        format: 'key-value',
      };
    }
  }

  private static checkSslMode(queryString: string | undefined | null): boolean {
    if (!queryString) return false;

    const params = new URLSearchParams(
      queryString.startsWith('?') ? queryString.slice(1) : queryString,
    );

    const ssl = params.get('ssl');
    const sslMode = params.get('sslMode');
    const sslModeHyphen = params.get('ssl-mode');
    const useSSL = params.get('useSSL');
    const sslaccept = params.get('sslaccept');

    if (ssl) return this.isSslEnabled(ssl);
    if (sslMode) return this.isSslEnabled(sslMode);
    if (sslModeHyphen) return this.isSslEnabled(sslModeHyphen);
    if (useSSL) return this.isSslEnabled(useSSL);
    if (sslaccept) return sslaccept.toLowerCase() === 'strict';

    return false;
  }

  private static isSslEnabled(sslValue: string | null | undefined): boolean {
    if (!sslValue) return false;

    const lowercased = sslValue.toLowerCase();
    const enabledValues = ['true', 'required', 'verify_ca', 'verify_identity', 'yes', '1'];
    return enabledValues.includes(lowercased);
  }
}
