require 'thwait'

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

module Pact
  module MockService
    class AppManager

      include Pact::Logging

      include Singleton

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

      def register_mock_service_for name, url, options = {}
        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(Pact::MockService.new(log_file: create_log_file(name), name: name, pact_dir: pact_dir, pact_specification_version: options[:pact_specification_version]), 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 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

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

      private

      def existing_app_on_port port
        app_registration = registration_on_port port
        app_registration ? app_registration.app : nil
      end

      def registration_on_port port
        @app_registrations.find { |app_registration| app_registration.port == port }
      end

      def pact_dir
        Pact.configuration.pact_dir
      end

      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

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

      def kill
        logger.debug "Supposed to be stopping"
        @spawned = false
      end

      def not_spawned?
        !spawned?
      end

      def spawned?
        @spawned
      end

      def is_a_mock_service?
        app.is_a? Pact::MockService::App
      end

      def to_s
        "#{app} on port #{port}"
      end

      def spawn
        logger.info "Starting app #{self}..."
        @server = Pact::Server.new(app, port).boot
        @spawned = true
        logger.info "Started on port #{port}"
      end
    end
  end
end