require 'uri'
require 'logger'

module JiraBox
  class Client

    API_VERSIONS = ['1', '2', 'latest'].freeze
    AUTH_TYPES   = [:basic, :oauth].freeze
    SSL_VERSIONS = ['TLSv1', 'SSLv3'].freeze

    DEFAULT_OPTIONS = {
      site: 'http://localhost:8080',
      api_version: 'latest',
      rest_api_prefix: 'rest/api',
      context_path: '/',
      username: nil,
      password: nil,
      ssl: {},
      auth_type: :basic,
      debug: false,
      logger: nil
    }.freeze

    attr_reader :options, :connection

    def initialize(options = {})
      @options = DEFAULT_OPTIONS.merge(options)
      validate_options!

      create_connection
    end

    def create_connection
      @connection = ConnectionFactory.new(options.merge(url: instance_url)).resolve!
    end

    def instance_url
      @instance_url ||= File.join(options[:site], options[:context_path], options[:rest_api_prefix], options[:api_version])
    end

    def api
      @api ||= RestAPI.new(connection)
    end

    def fields
      @fields ||= FieldHelpers.new
    end


    def bug_field_definitions(projectKey)
      result = api.issue.createmeta(projectKeys: projectKey, expand: ['projects.issuetypes.fields'])
      result[:projects].first[:issuetypes].select { |it| it['name'].eql?('Bug') }.first.try(:[], 'fields')
    end

    def create_bug(projectKey)
      JiraBug.create(projectKey, self.api)
    end

    def build_bug(projectKey, field_defs)
      bug_factory = BugFactory.new(projectKey, field_defs)
      bug_factory.build_definition
      bug_factory
    end


    private
      def validate_options!
        validate_api_version
        validate_site
        validate_auth_type
        validate_ssl

        [:context_path, :rest_api_prefix].tap do |opts|
          opts << :username << :password if options[:auth_type].eql?(:basic)
        end.each { |field| validate_type_string(field) }
      end

      def validate_ssl
        raise(JiraBox::Errors::InvalidOptionError.new('ssl must be a Hash')) if options[:ssl].present? && !options[:ssl].is_a?(Hash)
        ssl         = options[:ssl].slice(:verify, :version)
        ssl_verify  = ssl[:verify]
        ssl_version = ssl[:version]

        if ssl_verify.present? && (!ssl_verify.is_a?(TrueClass) || !ssl_verify.is_a?(FalseClass))
          raise(JiraBox::Errors::InvalidOptionError.new("ssl verify must be a boolean"))
        end

        if ssl_version.present? && !SSL_VERSIONS.include?(ssl_version)
          raise(JiraBox::Errors::InvalidOptionError.new("ssl version must be one of #{SSL_VERSIONS.join(', ')}"))
        end

        options[:ssl] = ssl
      end

      def validate_site
        options[:site].tap do |site|
          validate_type_string(:site)
          begin
            parsed = URI.parse(site)
            raise(URI::InvalidURIError.new) unless parsed.scheme.present? && parsed.host.present?
          rescue URI::InvalidURIError => ex
            raise(JiraBox::Errors::InvalidOptionError.new("site must be a valid url: #{site} is invalid"))
          end
        end
      end

      def validate_auth_type
        auth_type = options[:auth_type]
        unless auth_type.present? && AUTH_TYPES.include?(auth_type)
          raise(JiraBox::Errors::InvalidOptionError.new("invalid auth_type: #{auth_type}, must be one of #{AUTH_TYPES.join(', ')}"))
        end
      end

      def validate_type_string(field)
        options[field].tap do |f|
          unless f.present? && f.is_a?(String)
            raise(JiraBox::Errors::InvalidOptionError.new("#{field.to_s} must be a string"))
          end
        end
      end

      def validate_api_version
        options[:api_version].tap do |version|
          unless version.present? && API_VERSIONS.include?(version)
            raise(JiraBox::Errors::InvalidOptionError.new("api_version must be one of: #{API_VERSIONS.join(', ')}"))
          end
        end
      end

  end
end
