require 'thread'

class Indocker::DeploymentProgress
  def initialize(logger)
    @logger = logger

    if logger
      @logger.formatter = Proc.new do |severity, datetime, progname, msg|
        "#{msg}\n"
      end
    end

    @progress = Hash.new(:waiting)
    @semaphore = Mutex.new

    @synced_binaries = {
      # Structure:
      # server => {
      #   start: time,
      #   finish: time,
      #   state: (:waiting|:in_progress|:finished)
      # }
    }

    @synced_env_files = {
      # Structure:
      # env_file => {
      #   server => {
      #     start: time,
      #     finish: time,
      #     state: (:waiting|:in_progress|:finished)
      #   }
      # }
    }

    @synced_artifacts = {
      # Structure:
      # artifact => {
      #   server => {
      #     start: time,
      #     finish: time,
      #     state: (:waiting|:in_progress|:finished)
      #   }
      # }
    }

    @synced_repositories = {
      # Structure:
      # repository => {
      #   server => {
      #     start: time,
      #     finish: time,
      #     state: (:waiting|:in_progress|:finished)
      #   }
      # }
    }

    @deployed_containers = {
      # Structure:
      # container => {
      #   server => {
      #     build_start: time,
      #     build_finish: time,
      #     deploy_start: time,
      #     deploy_finish: time,
      #     state: (:waiting|:building|:waiting_deployment|:deploying|:finished)
      #   }
      # }
    }
  end

  def setup(binaries_servers:, build_servers:, deploy_servers:, env_files:, artifact_servers:,
            repositories:, force_restart:, skip_build:, containers:)
    @force_restart = force_restart
    @skip_build = skip_build

    binaries_servers.each do |server|
      @synced_binaries[server] = {
        start: nil,
        finish: nil,
        state: :waiting
      }
    end

    repositories.each do |repository|
      @synced_repositories[repository] = {}

      build_servers.each do |server|
        @synced_repositories[repository][server] = {
          start: nil,
          finish: nil,
          state: :waiting
        }
      end
    end

    env_files.each do |env_file|
      @synced_env_files[env_file] = {}

      deploy_servers.each do |server|
        @synced_env_files[env_file][server] = {
          start: nil,
          finish: nil,
          state: :waiting
        }
      end
    end

    artifact_servers.each do |artifact, servers|
      @synced_artifacts[artifact] = {}

      servers.each do |server|
        @synced_artifacts[artifact][server] = {
          start: nil,
          finish: nil,
          state: :waiting
        }
      end
    end

    @containers = containers

    containers.each do |container|
      @deployed_containers[container] = {}

      container.servers.each do |server|
        @deployed_containers[container][server] = {
          build_start: nil,
          build_finish: nil,
          deploy_start: nil,
          deploy_finish: nil,
          state: :waiting
        }
      end
    end

    log
  end

  def start_syncing_binaries(server)
    @semaphore.synchronize do
      @synced_binaries[server][:start] = Time.now
      @synced_binaries[server][:state] = :in_progress
      log
    end
  end

  def finish_syncing_binaries(server)
    @semaphore.synchronize do
      @synced_binaries[server][:finish] = Time.now
      @synced_binaries[server][:state] = :finished
      log
    end
  end

  def start_syncing_env_file(server, env_file)
    @semaphore.synchronize do
      @synced_env_files[env_file][server][:start] = Time.now
      log
    end
  end

  def finish_syncing_env_file(server, env_file)
    @semaphore.synchronize do
      @synced_env_files[env_file][server][:finish] = Time.now
      @synced_env_files[env_file][server][:state] = :finished
      log
    end
  end

  def start_syncing_artifact(server, artifact)
    @semaphore.synchronize do
      @synced_artifacts[artifact][server][:start] = Time.now
      log
    end
  end

  def finish_syncing_artifact(server, artifact)
    @semaphore.synchronize do
      @synced_artifacts[artifact][server][:finish] = Time.now
      @synced_artifacts[artifact][server][:state] = :finished
      log
    end
  end

  def start_syncing_repository(server, repository)
    @semaphore.synchronize do
      @synced_repositories[repository][server][:start] = Time.now
      log
    end
  end

  def finish_syncing_repository(server, repository)
    @semaphore.synchronize do
      @synced_repositories[repository][server][:finish] = Time.now
      @synced_repositories[repository][server][:state] = :finished
      log
    end
  end

  def start_building_container(container)
    @semaphore.synchronize do
      time = Time.now

      @deployed_containers[container].each do |server, data|
        next if data[:build_start]
        data[:build_start] = time
        data[:state] = :building
      end

      log
    end
  end

  def finish_building_container(container)
    @semaphore.synchronize do
      time = Time.now

      @deployed_containers[container].each do |server, data|
        next if data[:build_finish]
        data[:build_finish] = time
        data[:state] = :waiting_deployment
      end

      log
    end
  end

  def start_deploying_container(container, server)
    @semaphore.synchronize do
      @deployed_containers[container][server][:deploy_start] = Time.now
      @deployed_containers[container][server][:state] = :deploying
      log
    end
  end

  def finish_deploying_container(container, server)
    @semaphore.synchronize do
      @deployed_containers[container][server][:deploy_finish] = Time.now
      @deployed_containers[container][server][:state] = :finished
      log
    end
  end

  def log
    return if !@logger
    system("clear")

    if @skip_build
      @logger.info("Warning: Image build is skipped for all containers".purple)
    end

    if @force_restart
      @logger.info("Warning: All containers will be force restarted".purple)
    end

    # BINARIES formatter
    synced_binaries_servers = []
    not_synced_binaries_servers = []

    @synced_binaries.each do |server, data|
      if data[:state] == :finished
        synced_binaries_servers << server
      else
        not_synced_binaries_servers << server
      end
    end

    if !synced_binaries_servers.empty?
      @logger.info("Binaries synced:".green)

      synced_binaries_servers.each do |server|
        data = @synced_binaries[server]
        total = ", total: #{(data[:finish] - data[:start]).round}s" if data[:finish]
        @logger.info("  - #{server.name.to_s.yellow}#{total}")
      end
    end

    if !not_synced_binaries_servers.empty?
      @logger.info("Binaries syncing:".green)

      not_synced_binaries_servers.each do |server|
        data = @synced_binaries[server]
        @logger.info("  - #{server.name.to_s.cyan} (#{data[:state]}...)")
      end
    end

    # ENV FILES formatter
    synced_env_files = []
    not_synced_env_files = []

    @synced_env_files.each do |env_file, server_info|
      server_info.each do |server, data|
        if data[:state] == :finished
          if !synced_env_files.include?(env_file)
            synced_env_files << env_file
          end
        else
          if !not_synced_env_files.include?(env_file)
            not_synced_env_files << env_file
          end
        end
      end
    end

    if !synced_env_files.empty?
      @logger.info("ENV files synced:".green)

      synced_env_files.each do |env_file|
        servers_data = @synced_env_files[env_file]

        servers_data.each do |server, data|
          next if data[:state] != :finished
          total = ", total: #{(data[:finish] - data[:start]).round}s" if data[:finish]
          @logger.info("  - #{env_file.to_s.yellow}, server: #{server.name}#{total}")
        end
      end
    end

    if !not_synced_env_files.empty?
      @logger.info("ENV files syncing:".green)

      not_synced_env_files.each do |env_file|
        servers_data = @synced_env_files[env_file]

        servers_data.each do |server, data|
          next if data[:state] == :finished
          @logger.info("  - #{env_file.to_s.cyan}, server: #{server.name} (#{data[:state]}...)")
        end
      end
    end

    # Artifacts formatter
    synced_artifact = []
    not_synced_artifact = []

    @synced_artifacts.each do |artifact, server_info|
      server_info.each do |server, data|
        if data[:state] == :finished
          if !synced_artifact.include?(artifact)
            synced_artifact << artifact
          end
        else
          if !not_synced_artifact.include?(artifact)
            not_synced_artifact << artifact
          end
        end
      end
    end

    if !synced_artifact.empty?
      @logger.info("Artifacts synced:".green)

      synced_artifact.each do |artifact|
        servers_data = @synced_artifacts[artifact]

        servers_data.each do |server, data|
          next if data[:state] != :finished
          total = ", total: #{(data[:finish] - data[:start]).round}s" if data[:finish]
          @logger.info("  - #{artifact.name.to_s.yellow}, server: #{server.name}#{total}")
        end
      end
    end

    if !not_synced_artifact.empty?
      @logger.info("Artifacts syncing:".green)

      not_synced_artifact.each do |artifact|
        servers_data = @synced_artifacts[artifact]

        servers_data.each do |server, data|
          next if data[:state] == :finished
          @logger.info("  - #{artifact.name.to_s.cyan}, server: #{server.name} (#{data[:state]}...)")
        end
      end
    end

    # REPOSITORIES formatter
    synced_repositories = []
    not_synced_repositories = []

    @synced_repositories.each do |repository, server_info|
      server_info.each do |server, data|
        if data[:state] == :finished
          if !synced_repositories.include?(repository)
            synced_repositories << repository
          end
        else
          if !not_synced_repositories.include?(repository)
            not_synced_repositories << repository
          end
        end
      end
    end

    if !synced_repositories.empty?
      @logger.info("Repositories synced:".green)

      synced_repositories.each do |repository|
        servers_data = @synced_repositories[repository]

        servers_data.each do |server, data|
          next if data[:state] != :finished
          total = ", total: #{(data[:finish] - data[:start]).round}s" if data[:finish]
          @logger.info("  - #{repository.to_s.yellow}, server: #{server.name}#{total}")
        end
      end
    end

    if !not_synced_repositories.empty?
      @logger.info("Repositories syncing:".green)

      not_synced_repositories.each do |repository|
        servers_data = @synced_repositories[repository]

        servers_data.each do |server, data|
          next if data[:state] == :finished
          @logger.info("  - #{repository.to_s.cyan}, server: #{server.name} (#{data[:state]}...)")
        end
      end
    end

    # Containers formatter
    deployed_containers = []
    not_deployed_containers = []

    @deployed_containers.each do |container, server_info|
      server_info.each do |server, data|
        if data[:state] == :finished
          if !deployed_containers.include?(container)
            deployed_containers << container
          end
        else
          if !not_deployed_containers.include?(container)
            not_deployed_containers << container
          end
        end
      end
    end

    if !deployed_containers.empty?
      @logger.info("Deployed containers:".green)

      deployed_containers = deployed_containers
        .sort_by { |container|
          @deployed_containers[container]
            .map { |server, data| data[:deploy_finish] }
            .reject(&:nil?)
            .sort
            .last
            .to_i
        }

      deployed_containers.each do |container|
        servers_data = @deployed_containers[container]

        servers_data.each do |server, data|
          next if data[:state] != :finished
          total = ", total: #{(data[:deploy_finish] - data[:build_start]).round}s"
          build = ", build: #{(data[:build_finish] - data[:build_start]).round}s"
          deploy = ", deploy: #{(data[:deploy_finish] - data[:deploy_start]).round}s"
          @logger.info("  - #{container.name.to_s.yellow}, server: #{server.name}#{build}#{deploy}#{total}")
        end
      end
    end

    if !not_deployed_containers.empty?
      @logger.info("To be deployed containers:".green)

      not_deployed_containers.each do |container|
        servers_data = @deployed_containers[container]

        servers_data.each do |server, data|
          next if data[:state] == :finished

          name = container.name.to_s

          name = if data[:state] == :waiting
            name
          else
            name.cyan
          end

          @logger.info("  - #{name}, server: #{server.name} (#{data[:state]}...)")
        end
      end
    end
  end

  private

  def format_time(time)
    time.strftime("%H:%M:%S")
  end
end