# encoding: UTF-8

require 'zip'
require 'fileutils'

module GoodData
  class NoProjectError < RuntimeError;
  end

  class Project
    USERSPROJECTS_PATH = '/gdc/account/profile/%s/projects'
    PROJECTS_PATH = '/gdc/projects'
    PROJECT_PATH = '/gdc/projects/%s'
    SLIS_PATH = '/ldm/singleloadinterface'

    attr_accessor :connection

    class << self
      # Returns an array of all projects accessible by
      # current user
      def all
        json = GoodData.get GoodData.profile.projects
        json['projects'].map do |project|
          Project.new project
        end
      end

      # Returns a Project object identified by given string
      # The following identifiers are accepted
      #  - /gdc/md/<id>
      #  - /gdc/projects/<id>
      #  - <id>
      #
      def [](id)
        return id if id.respond_to?(:is_project?) && id.is_project?
        if id == :all
          Project.all
        else
          if id.to_s !~ /^(\/gdc\/(projects|md)\/)?[a-zA-Z\d]+$/
            raise ArgumentError.new('wrong type of argument. Should be either project ID or path')
          end

          id = id.match(/[a-zA-Z\d]+$/)[0] if id =~ /\//

          response = GoodData.get PROJECT_PATH % id
          Project.new response
        end
      end

      # Create a project from a given attributes
      # Expected keys:
      # - :title (mandatory)
      # - :summary
      # - :template (default /projects/blank)
      #
      def create(attributes, &block)
        GoodData.logger.info "Creating project #{attributes[:title]}"

        auth_token = attributes[:auth_token] || GoodData.connection.auth_token
        fail "You have to provide your token for creating projects as :auth_token parameter" if auth_token.nil? || auth_token.empty?

        json = {"project" =>
                  {
                    'meta' => {
                      'title' => attributes[:title],
                      'summary' => attributes[:summary] || 'No summary'
                    },
                    'content' => {
                      'guidedNavigation' => 1,
                      'authorizationToken' => auth_token,
                      'driver' => 'Pg'
                    }
                  }
        }
        json["project"]['meta']['projectTemplate'] = attributes[:template] if attributes[:template] && !attributes[:template].empty?
        project = Project.new json
        project.save

        # until it is enabled or deleted, recur. This should still end if there is a exception thrown out from RESTClient. This sometimes happens from WebApp when request is too long
        while project.state.to_s != "enabled"
          if project.state.to_s == "deleted"
            # if project is switched to deleted state, fail. This is usually problem of creating a template which is invalid.
            fail "Project was marked as deleted during creation. This usually means you were trying to create from template and it failed."
          end
          sleep(3)
          project.reload!
        end

        if block
          GoodData::with_project(project) do |p|
            block.call(p)
          end
        end
        project
      end

    end

    def initialize(json)
      @json = json
    end

    def save
      response = GoodData.post PROJECTS_PATH, raw_data
      if uri == nil
        response = GoodData.get response['uri']
        @json = response
      end
    end

    def saved?
      !!uri
    end

    def reload!
      if saved?
        response = GoodData.get(uri)
        @json = response
      end
      self
    end

    def delete
      raise "Project '#{title}' with id #{uri} is already deleted" if state == :deleted
      GoodData.delete(uri)
    end

    def uri
      data['links']['self'] if data && data['links'] && data['links']['self']
    end

    def browser_uri(options={})
      ui = options[:ui]
      if ui
        GoodData.connection.url + '#s=' + uri
      else
        GoodData.connection.url + uri
      end
    end

    def obj_id
      uri.split('/').last
    end

    alias :pid :obj_id

    def title
      data['meta']['title'] if data['meta']
    end

    def state
      data['content']['state'].downcase.to_sym if data['content'] && data['content']['state']
    end

    def md
      @md ||= Links.new GoodData.get(data['links']['metadata'])
    end

    # Creates a data set within the project
    #
    # == Usage
    # p.add_dataset 'Test', [ { 'name' => 'a1', 'type' => 'ATTRIBUTE' ... } ... ]
    # p.add_dataset 'title' => 'Test', 'columns' => [ { 'name' => 'a1', 'type' => 'ATTRIBUTE' ... } ... ]
    #
    def add_dataset(schema_def, columns = nil, &block)
      schema = if block
                 builder = block.call(Model::SchemaBuilder.new(schema_def))
                 builder.to_schema
               else
                 sch = {:title => schema_def, :columns => columns} if columns
                 sch = Model::Schema.new schema_def if schema_def.is_a? Hash
                 sch = schema_def if schema_def.is_a?(Model::Schema)
                 raise ArgumentError.new('Required either schema object or title plus columns array') unless schema_def.is_a? Model::Schema
                 sch
               end
      Model.add_schema(schema, self)
    end

    def add_metric(options={})
      expression = options[:expression] || fail('Metric has to have its expression defined')
      m1 = GoodData::Metric.create(options)
      m1.save
    end

    def add_report(options={})
      rep = GoodData::Report.create(options)
      rep.save
    end

    def add_dashboard(options={})
      dash = GoodData::Dashboard.create(options)
      dash.save
    end

    def add_user(email_address, domain)
      raise 'Not implemented'
    end

    def upload(file, schema, mode = 'FULL')
      schema.upload file, self, mode
    end

    def slis
      link = "#{data['links']['metadata']}#{SLIS_PATH}"

      # TODO: Review what to do with passed extra argument
      Metadata.new GoodData.get(link)
    end

    def datasets
      datasets_uri = "#{md['data']}/sets"
      response = GoodData.get datasets_uri
      response['dataSetsInfo']['sets'].map do |ds|
        DataSet[ds['meta']['uri']]
      end
    end

    def raw_data
      @json
    end

    def data
      raw_data['project']
    end

    def links
      data['links']
    end

    def to_json
      raw_data.to_json
    end

    def is_project?
      true
    end

    def partial_md_export(objects, options={})
      # TODO: refactor polling to md_polling in client

      fail "Nothing to migrate. You have to pass list of objects, ids or uris that you would like to migrate" if objects.nil? || objects.empty?
      target_project = options[:project]
      fail "You have to provide a project instance or project pid to migrate to" if target_project.nil?
      target_project = GoodData::Project[target_project]
      objects = objects.map {|obj| GoodData::MdObject[obj]}
      GoodData.logging_on
      export_payload = {
          :partialMDExport => {
              :uris => objects.map {|obj| obj.uri}
          }
      }
      result = GoodData.post("#{GoodData.project.md['maintenance']}/partialmdexport", export_payload)
      polling_url = result["partialMDArtifact"]["status"]["uri"]
      token = result["partialMDArtifact"]["token"]

      polling_result = GoodData.get(polling_url)
      while polling_result["wTaskStatus"]["poll"]["status"] == "RUNNING"
        polling_result = GoodData.get(polling_url)
      end
      fail "Exporting objects failed" if polling_result["wTaskStatus"]["poll"]["status"] == "ERROR"

      import_payload = {
      :partialMDImport => {
        :token => token,
         :overwriteNewer => "1",
         :updateLDMObjects => "0"
        }
      }

      result = GoodData.post("#{target_project.md['maintenance']}/partialmdimport", import_payload)
      polling_uri = result["uri"]
      polling_result = GoodData.get(polling_url)
      while polling_result["wTaskStatus"]["poll"]["status"] == "RUNNING"
        polling_result = GoodData.get(polling_url)
      end
      fail "Exporting objects failed" if polling_result["wTaskStatus"]["poll"]["status"] == "ERROR"

    end

  end
end