require "db/mongo/models/environment/environment"
require "db/mongo/models/user"
require "db/mongo/models/environment/environments_array"
require "json"
require "hooks"
require "lib/project/type/types_factory"
require "exceptions/invalid_record"
require "exceptions/record_not_found"
#require "db/validators/environment/environments"
require "db/validators/project/named_tasks"

module Devops
  module Model
    class Project

      include Hooks
      include Hooks::InstanceHooks
      define_hook :before_create
      define_hook :after_create

      define_hook :before_delete
      define_hook :after_delete

      include ::Mongoid::Document
      include ::Mongoid::Timestamps::CreatedInt
      include ::ActiveModel::Validations

      #attr_accessor :components
      store_in collection: "projects"

      field :_id, as: :'id', overwrite: true, type: String
      field :environments, type: EnvironmentsArray, default: ->{ [] }
      field :type, type: String
      field :owner, type: String
      field :project_users, type: Array, default: ->{ [] }
      field :archived, type: Boolean
      field :description, type: String
      field :run_list, type: Array, default: ->{ [] }
      field :named_tasks, type: Array, default: ->{ [] }

      MAX_ID_LEN = 100
      ID_REGEX = /\A[\w\-\.]{1,#{MAX_ID_LEN}}\z/

      validates_presence_of :id, message: "'id' is undefined"
      validates_length_of :id, minimum: 1, maximum: MAX_ID_LEN
      validates_format_of :id, with: ID_REGEX, message: "invalid id, it should contains 'a-zA-Z0-9-.' symbols"
      validates_uniqueness_of :id, on: :create

      validates_length_of :description, maximum: 500

      validates_presence_of :owner, message: "'owner' is undefined"
      validates :named_tasks, proper_named_tasks_format: true


=begin
TODO:

      set_field_validators :named_tasks, [::Validators::FieldValidator::Nil,
                                    ::Validators::FieldValidator::FieldType::Array,
                                    ::Validators::FieldValidator::NamedTasks]

      set_validators ::Validators::DeployEnv::RunList,
                     ::Validators::DeployEnv::DeployEnvs
=end
      validates_length_of :description, maximum: 500

      #TODO:  self.named_tasks = p["named_tasks"] || []
      def initialize hash
        raise InvalidRecord.new("Parameter 'id' is not a string") unless hash["id"].is_a?(String)
        raise InvalidRecord.new("Parameter 'environments' is not an array") unless hash["environments"].is_a?(Array) or hash["environments"].nil?
        raise InvalidRecord.new("Parameter 'run_list' is not an array") unless hash["run_list"].is_a?(Array) or hash["run_list"].nil?
        raise InvalidRecord.new("Parameter 'project_users' is not an array") unless hash["project_users"].is_a?(Array) or hash["project_users"].nil?
        super(hash)
      end

      def self.find_with_environment id, environment
        Project.find_by({_id: id, environments:{'$elemMatch' => {id: environment}}})
      end

      def self.find_with_category id, environment, category
        Project.find_by(
          _id: id,
          environments: {
            '$elemMatch' => {
              id: environment,
              categories: {'$elemMatch' => {id: category}}
            }
          }
        )
      end

      # TODO: extract to handler or parser
      def self.list_fields
        ['deploy_envs', 'type', 'description', 'named_tasks']
      end

      def self.check_user_authorization project_id, env, user_id
#        find({'_id' => project_id}).projection({environments:{'$elemMatch' => {id:env}}})
        project = find(project_id)
        if !project.check_authorization(user_id, env)
          raise Devops::Exception::Unauthorized.new("User '#{user_id}' is unauthorized to work with project '#{project_id}' and environment '#{env}'")
        end
        project
      rescue Mongoid::Errors::DocumentNotFound
        raise Devops::Exception::RecordNotFound.new("Project with id '#{project_id}' not found")
      end

      def environment env
        de = self.environments.detect {|e| e.id == env}
        raise Devops::Exception::RecordNotFound.new("Project '#{self.id}' does not have deploy environment '#{env}'") if de.nil?
        de
      end

      def add_environment environment
        self.push(environments: environment.to_hash)
        DevopsLogger.logger.info("Added environment '#{environment.id}' to project '#{self.id}'")
        DevopsLogger.logger.debug("Added environment: #{environment.to_hash.inspect}")
      end

      def add_category env, category
        env.categories.push category
        category_hash = category.to_hash
        Project.where('_id' => self.id, 'environments.id' => env.id).push('environments.$.categories' => category_hash)
        DevopsLogger.logger.debug("Added category '#{category.id}' to project '#{self.id}' and environment '#{env.id}'")
        DevopsLogger.logger.debug("Added category: #{category_hash.inspect}")
        category
      end

      def delete_category env, category_id
        bsize = env.categories.size
        env.categories.delete_if{|c| c.id == category_id}
        if bsize == env.categories.size
          raise Devops::Exception::RecordNotFound.new("Category '#{category_id}' in project '#{id}' and deploy environment '#{env}' does not exist")
        else
          Project.where({'_id' => self.id, 'environments.id' => env.id}).pull('environments.$.categories' => {'id' => category_id})
          DevopsLogger.logger.info("Category '#{category_id}' removed from project '#{self.id}' and environment '#{env.id}'")
        end
      end

      # user could be a String or an Array of strings.
      # if env is nil, add given user(s) to all project's envs,
      # otherwise only to specified one.
      def add_authorized_user user, env=nil
        return if user.nil?
        new_users = ( user.is_a?(Array) ? user : [ user ] )
        environments = env.nil? ? self.environments : [ self.environment(env) ]
        environments.each do |e|
          e.add_users new_users
          Devops::Db.connector.set_project_environment_field(self.id, e.id, {users: e.users})
        end
      end

      def remove_authorized_user user, env=nil
        return if user.nil?
        users = ( user.is_a?(Array) ? user : [ user ] )
        environments = env.nil? ? self.environments : [ self.environment(env) ]
        environments.each do |e|
          e.users = e.users - users
          Devops::Db.connector.set_project_environment_field(self.id, e.id, {users: e.users})
        end
      end

      def check_authorization user_id, env
        return true if user_id == self.owner or user_id == User::ROOT_USER_NAME
        return true if is_sandbox?
        e = self.environment(env)
        return e.users.include? user_id
      rescue Devops::Exception::RecordNotFound => e
        return false
      end

      def is_sandbox?
        id.start_with?('sandbox-')
      end

      def delete_environment env
        self.environments.delete_if {|e| e.id == env}
        Project.where('_id' => self.id).pull('environments' => {'id' => env})
      end

      def to_hash
        hash = self.attributes.clone
        hash["id"] = hash.delete("_id")
        hash.delete('archived') unless hash['archived']
        hash
      end

      # TODO: why symbols here?
      def to_hash_list
        {
          id: self.id,
          description: self.description,
          owner: self.owner,
          run_list: self.run_list,
          project_users: self.project_users,
          created_at: self.created_at
        }
      end

      def deploy_info environment, build_number=nil
        {
          "use_json_file" => false,
#          "run_list" => Set.new.merge(self.run_list).merge(environment.run_list).to_a,
          "project" => self.id,
          "project_info" => {
            "project" => self.id,
            "environment" => environment.id,
            "deployers" => environment.users
          }
        }
      end

=begin
      def extract_servers env, params
        @handler.extract_servers(self, env, params)
      end

      # maybe it worth to move components functionality to devops-nibr?
      #TODO: create validator
      def validate_components
        raise InvalidRecord.new "Components is not a hash" unless self.components.is_a?(Hash)
        self.components.each do |id, c|
          raise InvalidRecord.new "Component '#{id}' has no attribute 'fileid'" unless c.key?("fileid")
        end
      end
=end

      # def self.create_roles_response roles
      #   return "error in creating roles" unless roles
      #   info = ''
      #   info += "Project roles '#{roles[:new].join("', '")}' have been automaticaly created" unless roles[:new].nil?
      #   info += " Project roles '#{roles[:exist].join("', '")}' weren't created because they exist" unless roles[:exist].nil?
      #   info += " Project roles '#{roles[:error].join("', '")}' weren't created because of internal error" unless roles[:error].nil?
      #   info
      # end

    end
  end
end
