require "lib/executors/server_executor"
require "app/api3/parsers/stack"
require 'db/mongo/models/stack/stack_factory'
require 'workers/stack_bootstrap_worker'
require_relative "request_handler"

module Devops
  module API3
    module Handler
      class Stack < RequestHandler

        set_parser Devops::API3::Parser::StackParser

        def stacks
          Model::StackAws.all
        end

        # We support only aws, actually
        def stacks_for_provider provider
          Model::StackAws.all
        end

        def create_stack
          object = parser.create
          attrs = object['stack_attributes']
          env, category = fetch_env_and_category(attrs)
          fill_attributes_from_category(attrs, env, category)
          jid = Worker.start_async(StackBootstrapWorker,
            stack_attributes: attrs,
            without_bootstrap: object['without_bootstrap'],
            skip_rollback: object['skip_rollback']
          )
          [jid]
        end

        def stack name
          Model::StackAws.where(name: name).first
        end

        def delete_stack name
          stack = self.stack(name)
          stack.delete_stack_in_cloud!
          Model::Server.where(stack: stack.id).delete_all
          stack.delete
        end

        def sync id
          stack = self.stack(id)
          stack.sync_status_and_events!
          Devops::Db.connector.stack_update(stack)

          stack
        end

        def stack_servers name
          stack = Devops::Db.connector.stack(name)
          Devops::Db.connector.stack_servers(stack.id)
        end

        def set_run_list id
          Devops::Db.connector.set_stack_run_list(id, parser.run_list)
        end

        def set_tags id
          tags = parser.tags
          prepare_tags do |id, provider|
            provider.set_tags id, tags
          end
        end

        def unset_tags id
          tags = parser.tags
          prepare_tags do |id, provider|
            provider.unset_tags id, tags
          end
        end

        def prepare_tags node_name
          stack = Devops::Db.connector.stack(id)
          Devops::Db.connector.check_project_auth stack.project, stack.deploy_env, parser.current_user
          stack_servers = Devops::Db.connector.stack_servers(id)
          provider = stack.provider_instance
          stack_servers.each do |s|
            yield s.id, provider
          end
        end

        def deploy id
          stack = self.stack(id)
          owner = parser.current_user
          project = Devops::Db.connector.check_project_auth(stack.project, stack.deploy_env, owner)
          deploy_env_model = project.deploy_env(stack.deploy_env)
          body = parser.deploy
          names = body["names"]
          tags = body["tags"] || []
          files = []
          jid = nil
          servers = Devops::Db.connector.stack_servers(id, true)
          servers.each do |s|
            begin
              deploy_info = project.deploy_info(deploy_env_model, nil)
              jid = Worker.start_async(DeployWorker,
                server_attrs: s.to_hash,
                owner: owner,
                tags: tags,
                deploy_info: deploy_info
              )
            rescue DeployInfoError => e
              msg = "Can not get deploy info: " + e.message
              DevopsLogger.logger.error msg
              jid = "error_#{s.chef_node_name}_#{Time.new.to_i}"
              dir = DevopsConfig.config[:report_dir_v2]
              file = File.jon(dir, jid)
              File.open(file, "w") do |out|
                out.write msg
              end
              o = {
                "file" => file,
                "_id" => jid,
                "created_by" => owner,
                "project" => s.project,
                "deploy_env" => s.deploy_env,
                "type" => JobTask::DEPLOY_TYPE,
                "status" => Worker::STATUS::FAILED
              }
              JobTask.create(o)
            end
            files.push jid
          end
          files
        end

        def deploy_stream id, out
          stack = self.stack(id)
          owner = parser.current_user
          status = []
          project = Devops::Db.connector.check_project_auth(stack.project, stack.deploy_env, owner)
          body = parser.deploy
          #TODO: deploy servers names
#          names = body["names"]
          tags = body["tags"] || []
          # find all stack reserved servers
          servers = Devops::Db.connector.stack_servers(stack_id, true)
          deploy_info = project.deploy_info(stack.deploy_env, nil)
          servers.each do |s|
            begin
              res = Devops::Executor::ServerExecutor.new(s, out, current_user: owner).deploy_server_with_tags(tags, deploy_info)
              status.push(res)
            rescue DeployInfoError => e
              msg = "Can not get deploy info: " + e.message
              DevopsLogger.logger.error msg
              out << msg + "\n"
              status.push 2
              next
            end
          end
          status
        rescue RecordNotFound  => e
          out << e.message
          [-10]
        end

        def reserve_servers(stack_id)
          stack = self.stack(stack_id)
          user = parser.current_user
          Devops::Db.connector.check_project_auth stack.project, stack.deploy_env, user

          # check if none of servers are reserved by somebody else
          servers = Devops::Db.connector.stack_servers(stack_id)
          servers.each do |server|
            if server.reserved_by.present? && server.reserved_by != user
              raise ConflictException.new("Server '#{server.chef_node_name}' already reserved by #{server.reserved_by}")
            end
          end

          # reserve them
          servers.each do |server|
            server.reserved_by = user
            Devops::Db.connector.server_update(server)
          end
        end

        def unreserve_servers(stack_id)
          stack = self.stack(stack_id)
          user = parser.current_user
          Devops::Db.connector.check_project_auth stack.project, stack.deploy_env, user

          servers = Devops::Db.connector.stack_servers(stack_id)
          servers.each do |server|
            server.reserved_by = nil
            Devops::Db.connector.server_update(server)
          end
        end

        def change_stack_template(stack_id)
          stack_template_id = parser.change_stack_template
          Devops::Db.connector.stack_template(stack_template_id)
          stack = Devops::Db.connector.stack(stack_id)
          stack.change_stack_template!(stack_template_id)
        end

        private

        # env is used in devops-nibr
        def fill_attributes_from_category(stack_attrs, env, category)
          stack_attrs['stack_template'] = category.provider.stack_template
          stack_attrs['owner'] = parser.current_user
          stack_attrs['provider'] = category.provider.name
          stack_attrs['provider_account'] = category.provider.account
        end

        def fetch_env_and_category(attrs)
          project = Model::Project.find_with_category(*attrs.values_at('project', 'environment', 'category'))
          unless project
            raise InvalidRecord.new("Project '#{attrs['project']}' has no env '#{attrs['environment']}' or category '#{attrs['category']}")
          end
          env = project.environment(attrs['environment'])
          category = env.get_category!(attrs['category'])
          if category.provider.type != Model::CategoryProvider::STACK_TYPE
            raise InvalidRecord.new("Category '#{category.id}' type isn't 'stack'")
          end
          [env, category]
        end

      end
    end
  end
end
