# frozen_string_literal: true

require "cgi"

module Hanami
  module Model
    class Migrator
      # Sequel connection wrapper
      #
      # Normalize external adapters interfaces
      #
      # @since 0.5.0
      # @api private
      class Connection
        # @since 0.5.0
        # @api private
        def initialize(configuration)
          @configuration = configuration
        end

        # @since 0.7.0
        # @api private
        def raw
          @raw ||= begin
                     Sequel.connect(
                       configuration.url,
                       loggers: [configuration.migrations_logger]
                     )
                   rescue Sequel::AdapterNotFound
                     raise MigrationError.new("Current adapter (#{configuration.adapter.type}) doesn't support SQL database operations.")
                   end
        end

        # Returns DB connection host
        #
        # Even when adapter doesn't provide it explicitly it tries to parse
        #
        # @since 0.5.0
        # @api private
        def host
          @host ||= parsed_uri.host || parsed_opt("host")
        end

        # Returns DB connection port
        #
        # Even when adapter doesn't provide it explicitly it tries to parse
        #
        # @since 0.5.0
        # @api private
        def port
          @port ||= parsed_uri.port || parsed_opt("port").to_i.nonzero?
        end

        # Returns DB name from conenction
        #
        # Even when adapter doesn't provide it explicitly it tries to parse
        #
        # @since 0.5.0
        # @api private
        def database
          @database ||= parsed_uri.path[1..-1]
        end

        # Returns DB type
        #
        # @example
        #   connection.database_type
        #     # => 'postgres'
        #
        # @since 0.5.0
        # @api private
        def database_type
          case uri
          when /sqlite/
            :sqlite
          when /postgres/
            :postgres
          when /mysql/
            :mysql
          end
        end

        # Returns user from DB connection
        #
        # Even when adapter doesn't provide it explicitly it tries to parse
        #
        # @since 0.5.0
        # @api private
        def user
          @user ||= parsed_opt("user") || parsed_uri.user
        end

        # Returns user from DB connection
        #
        # Even when adapter doesn't provide it explicitly it tries to parse
        #
        # @since 0.5.0
        # @api private
        def password
          @password ||= parsed_opt("password") || parsed_uri.password
        end

        # Returns DB connection URI directly from adapter
        #
        # @since 0.5.0
        # @api private
        def uri
          @configuration.url
        end

        # Returns DB connection wihout specifying database name
        #
        # @since 0.5.0
        # @api private
        def global_uri
          uri.sub(parsed_uri.select(:path).first, "")
        end

        # Returns a boolean telling if a DB connection is from JDBC or not
        #
        # @since 0.5.0
        # @api private
        def jdbc?
          !uri.scan("jdbc:").empty?
        end

        # Returns database connection URI instance without JDBC namespace
        #
        # @since 0.5.0
        # @api private
        def parsed_uri
          @parsed_uri ||= URI.parse(uri.sub("jdbc:", ""))
        end

        # @api private
        def schema
          configuration.schema
        end

        # Return the database table for the given name
        #
        # @since 0.7.0
        # @api private
        def table(name)
          raw[name] if raw.tables.include?(name)
        end

        private

        # @since 1.0.0
        # @api private
        attr_reader :configuration

        # Returns a value of a given query string param
        #
        # @param option [String] which option from database connection will be extracted from URI
        #
        # @since 0.5.0
        # @api private
        def parsed_opt(option, query: parsed_uri.query)
          return if query.nil?

          @parsed_query_opts ||= CGI.parse(query)
          @parsed_query_opts[option].to_a.last
        end
      end
    end
  end
end
