require 'thwait'

require 'net/http'
require 'uri'
require 'find_a_port'
require 'pact/logging'
require 'pact/consumer/server'
require 'singleton'

module Pact
  module Consumer
    class AppManager

      include Pact::Logging

      include Singleton

      def initialize
        @apps_spawned = false
        @app_registrations = []
      end

      def register_mock_service_for name, url
        uri = URI(url)
        raise "Currently only http is supported" unless uri.scheme == 'http'
        raise "Currently only services on localhost are supported" unless uri.host == 'localhost'

        register(MockService.new(log_file: create_log_file(name), name: name), uri.port)
      end

      def register(app, port = FindAPort.available_port)
        existing = existing_app_on_port port
        raise "Port #{port} is already being used by #{existing}" if existing and not existing == app
        app_registration = register_app app, port
        app_registration.spawn if @apps_spawned
        port
      end

      def existing_app_on_port port
        app_registration = @app_registrations.find { |app_registration| app_registration.port == port }
        app_registration ? app_registration.app : nil
      end

      def app_registered_on?(port)
        app_registrations.any? { |app_registration| app_registration.port == port }
      end

      def ports_of_mock_services
        app_registrations.find_all(&:is_a_mock_service?).collect(&:port)
      end

      def kill_all
        app_registrations.find_all(&:spawned?).collect(&:kill)
        @apps_spawned = false
      end

      def clear_all
        kill_all
        @app_registrations = []
      end

      def spawn_all
        app_registrations.find_all(&:not_spawned?).collect(&:spawn)
        @apps_spawned = true
      end

      private

      def create_log_file service_name
        FileUtils::mkdir_p Pact.configuration.log_dir
        log = File.open(log_file_path(service_name), 'w')
        log.sync = true
        log
      end

      def log_file_path service_name
        File.join(Pact.configuration.log_dir, "#{log_file_name(service_name)}.log")
      end

      def log_file_name service_name
        lower_case_name = service_name.downcase.gsub(/\s+/, '_')
        if lower_case_name.include?('_service')
          lower_case_name.gsub('_service', '_mock_service')
        else
          lower_case_name + '_mock_service'
        end
      end

      def app_registrations
        @app_registrations
      end

      def register_app app, port
        app_registration = AppRegistration.new :app => app, :port => port
        app_registrations << app_registration
        app_registration
      end
    end

    class AppRegistration
      include Pact::Logging
      attr_accessor :port
      attr_accessor :app
      attr_accessor :pid

      def initialize opts
        @max_wait = 10
        @port = opts[:port]
        @pid = opts[:pid]
        @app = opts[:app]
      end

      def kill
        # TODO: need to work out how to kill
        # logger.info "Killing #{self}"
        # Process.kill(9, pid)
        # Process.wait(pid)
        # self.pid = nil
        self.pid = nil
      end

      def not_spawned?
        !spawned?
      end

      def spawned?
        self.pid != nil
      end

      def is_a_mock_service?
        app.is_a? MockService
      end

      def to_s
        "#{app} on port #{port}" + (@pid ? " with pid #{pid}" : "")
      end

      def spawn
        logger.info "Starting app #{self}..."
        Pact::Server.new(app, port).boot
        self.pid = 'unknown'
        logger.info "Started with pid #{pid}"
      end

      def wait_until
        waited = 0
        wait_time = 0.1
        while waited < @max_wait do
          break if yield
          sleep wait_time
          waited += wait_time
          raise "Waited longer than #{@max_wait} seconds" if waited >= @max_wait
        end
      end

    end
  end
end