require "fileutils"
require_relative "db"
require_relative "repomanage"
require_relative "mock"
require_relative "utilities"
require "digest"

PROJECTS_STRUCTURE = {
  :REPO => "repo",
  :LOGS => "logs",
  :CONFIGS => "configs",
  :SRCPRP => "srcprp",
  :SIGNED => "signed",
  :SRC => "src",
  :SNAP => "snapshot",
}

class ProjectsActions
  attr :path, :error, :db

  def initialize(path, db)
    @path = nil
    @error = nil
    @db = db
    if File.absolute_path?(path)
      if File.exist?(path)
        @path = path
      end
    else
      apath = File.realpath(path)
      if File.exist?(apath)
        @path = apath
      end
    end
  end

  def get_projects
    @db.proj_list
  end

  def get_project(id)
    @db.proj(id.to_i)
  end

  def get_project_path(id)
    @error = nil
    fname = nil
    prj = @db.proj(id)
    if prj.nil?
      @error = "Проекта с id = #{id} не существует"
    else
      fname = File.expand_path("#{prj[:projname]}.prj", @path)
    end
    fname
  end

  def get_project_config(id)
    @error = nil
    fname = nil
    prj = @db.proj(id)
    if prj.nil?
      @error = "Проекта с id = #{id} не существует"
    else
      fname = File.expand_path("#{prj[:projname]}.prj", @path)
      fname = File.join(fname, PROJECTS_STRUCTURE[:CONFIGS], "#{prj[:projname]}.cfg")
    end
    fname
  end

  def get_project_repo(id)
    proj_path = get_project_path(id)
    File.join(proj_path, PROJECTS_STRUCTURE[:REPO])
  end

  def get_project_snap(id, snap)
    proj_path = get_project_path(id)
    File.join(proj_path, PROJECTS_STRUCTURE[:SNAP], snap)
  end

  def get_project_path_git(id, gitname)
    proj_path = get_project_path(id)
    File.join(proj_path, PROJECTS_STRUCTURE[:SRC], gitname)
  end

  def generate_linked_repos(id, proj_path, proj_name, linked_repo_tpl)
    linked_prj = []
    proj_repo_path = File.join(proj_path, PROJECTS_STRUCTURE[:REPO])
    proj_repo = <<~PRJ_CFG
      config_opts['dnf.conf'] += """
      [#{proj_name}-repository]
      name=Project repository #{proj_name}
      baseurl=file://#{proj_repo_path}
      enabled=1
      priority=80
      skip_if_unavailable=True
      """
    PRJ_CFG
    linked_prj << proj_repo
    unless id.nil?
      link_prj_list = @db.get_projects_links(id)
      unless link_prj_list.nil?
        link_prj_list.each do |item|
          internal_repo = ProjectsActions.new(@path, @db)
          internal_path = internal_repo.get_project_path(item[:proj_id_repository])
          internal_repo_path = File.join(internal_path, PROJECTS_STRUCTURE[:REPO])
          internal_proj_info = internal_repo.get_project(item[:proj_id_repository])
          proj_repo = <<~PRJ_CFG
            config_opts['dnf.conf'] += """
            [#{internal_proj_info[:projname]}-repository]
            name=Project repository #{internal_proj_info[:projname]}
            baseurl=file://#{internal_repo_path}
            enabled=1
            skip_if_unavailable=True
            """
          PRJ_CFG
          linked_prj << proj_repo
        end
      end
    end
    File.open(linked_repo_tpl, "w") { |f| f << linked_prj.join("\n\n\n") }
  end

  def regenerate_linked_repos(id)
    proj_path = get_project_path(id)
    config_path = File.join(proj_path, PROJECTS_STRUCTURE[:CONFIGS])
    prj_incl_path = File.join(config_path, "repos_include.tpl")
    prj_info = get_project(id)
    unless prj_info.nil?
      generate_linked_repos(id, proj_path, prj_info[:projname], prj_incl_path)
    end
  end

  def generate_config(id, configuration_path, proj_path, proj_name)
    proj_conf_path = File.join(proj_path, PROJECTS_STRUCTURE[:CONFIGS], "#{proj_name}.cfg")
    conf_path = File.join(proj_path, PROJECTS_STRUCTURE[:CONFIGS])
    prj_incl_path = File.join(conf_path, "repos_include.tpl")
    proj_config = <<~PRJ_CFG
      include("#{configuration_path}")
      include("#{prj_incl_path}")
      config_opts['plugin_conf']['ccache_enable'] = False
      config_opts['plugin_conf']['ccache_opts']['max_cache_size'] = '4G'
      config_opts['plugin_conf']['ccache_opts']['hashdir'] = True
      config_opts['plugin_conf']['ccache_opts']['debug'] = False
      config_opts['plugin_conf']['ccache_opts']['show_stats'] = True
      config_opts['plugin_conf']['package_state_enable'] = True
      config_opts['plugin_conf']['procenv_enable'] = False
      config_opts['plugin_conf']['root_cache_enable'] = True
      config_opts['plugin_conf']['showrc_enable'] = True
      config_opts['plugin_conf']['yum_cache_enable'] = True
      config_opts['chroot_setup_cmd'] += " gcc gcc-c++ make"
    PRJ_CFG
    File.open(proj_conf_path, "w") { |f| f << proj_config }
    generate_linked_repos(id, proj_path, proj_name, prj_incl_path)
  end

  def create_project(name, description, configuration, nopublic, tmpbld)
    @error = nil
    ret_val = 0
    project_name = sanitize_rcptname(name)
    fname = File.expand_path("#{project_name}.prj", @path)
    if File.exist?(fname)
      @error = "Проект с таким именем уже существует: #{project_name}"
      ret_val = 1
    else
      created = false
      #begin
      Dir.mkdir(fname)
      PROJECTS_STRUCTURE.each_pair do |key, value|
        new_path = File.join(fname, value)
        Dir.mkdir(new_path)
      end
      if File.exist?(configuration)
        generate_config(nil, configuration, fname, project_name)
        @error = @db.proj_create(project_name, description, nopublic, tmpbld)
        if @error.nil?
          created = true
        end
        repo_path = File.join(fname, PROJECTS_STRUCTURE[:REPO])
        repoman = RepoManager.new(repo_path)
        repoman.create_repo
      else
        ret_val = 1
        @error = "Конфигурация #{configuration} не существует"
      end
      #rescue => e
      #  ret_val = 1
      #  @error = e.message
      #end
      unless created
        FileUtils.rm_rf(fname, secure: true)
      end
    end
    ret_val
  end

  def get_project_gits(id, repo)
    res = @db.get_gits_for_projects(id)
    res_sync = res.map do |item|
      prj_p = get_project_path(id)
      path = File.join(prj_p, PROJECTS_STRUCTURE[:SRC], item[:reponame])
      item[:is_repo_synced] = repo.is_repos_sync(item[:reponame], path)
      item
    end
    res
  end

  def add_git_to_project(prj_id, git_id, repo, git_name)
    path = get_project_path(prj_id)
    if @error.nil?
      path = File.join(path, PROJECTS_STRUCTURE[:SRC], git_name)
      err = repo.clone_repo_master(git_id, path)
      if err.nil?
        @db.save_git_project(prj_id, git_id)
      end
    else
      err = @error
    end
    err
  end

  def renew_git_to_project(prj_id, git_id, repo, git_name)
    path = get_project_path(prj_id)
    if @error.nil?
      path = File.join(path, PROJECTS_STRUCTURE[:SRC], git_name)
      err = repo.clone_repo_master(git_id, path)
    else
      err = @error
    end
    err
  end

  def get_related_projects_list(prj_id)
    links_list = []
    links = @db.get_projects_links(prj_id)
    unless links.nil?
      links_list = links.map do |item|
        prj_info = @db.proj(item[:proj_id_repository])
        if prj_info.nil?
          item[:list_state] = false
        else
          item[:list_state] = true
          item[:prj_info] = prj_info
        end
        item
      end.select { |item| item[:list_state] }
    end
    links_list
  end

  def delete_linked_projects(prj_id)
    @db.delete_linked_projects(prj_id)
  end

  def save_linked_projects(prj_id, new_ids, delete_ids)
    delete_ids.each { |item| @db.delete_linked_projects_with_id(prj_id, item) }
    new_ids.each do |item|
      @db.save_linked_projects(prj_id, item)
    end
  end

  def find_spec_file(prj_id, git_id)
    spec_file = ""
    proj_path = get_project_path(prj_id)
    git_name = @db.get_repo_info_by_id(git_id)
    git_source = File.join(proj_path, PROJECTS_STRUCTURE[:SRC], git_name[:reponame])
    spec = @db.get_project_repo_spec(prj_id, git_id)
    if spec.nil?
      spec_files = get_spec_files_in_dir(git_source)
      unless spec_files.nil?
        spec_file = spec_files.first
      end
    else
      spec_file = spec[:spec_name]
    end
    spec_file
  end

  def build_projects_git(prj_id, git_id, counter_file, build_lock)
    bld_id = 0
    build_ok = true
    proj_path = get_project_path(prj_id)
    git_name = @db.get_repo_info_by_id(git_id)
    prep_script = @db.get_git_recips(git_id)
    prepare_path = File.join(proj_path, PROJECTS_STRUCTURE[:SRCPRP], git_name[:reponame])
    spec_file = find_spec_file(prj_id, git_id)
    repo_lock = File.join(proj_path, PROJECTS_STRUCTURE[:CONFIGS], ".repolock")
    if File.exist?(prepare_path)
      lockf_path = File.join(prepare_path, "lock")
      File.open(lockf_path, File::RDWR | File::CREAT) do |f|
        result = f.flock(File::LOCK_EX | File::LOCK_NB)
        if result == false
          # Файл заблокирован считать id и вывести сведения о сборке
          build_ok = false
          build_id = f.gets
          build_id = build_id.strip.to_i unless build_id.nil?
          if build_id.positive?
            build_info = @db.get_build_task_process_log(build_id)
            bld_id = build_info[:id] unless build_info.nil?
          end
        else
          # Сборка завершилась, но каталог не подчистился
          FileUtils.rm_rf(prepare_path)
          f.flock(File::LOCK_UN)
          build_ok = true
        end
      end
    end

    #Верная ситуация
    if build_ok
      build_path = File.join(proj_path, PROJECTS_STRUCTURE[:LOGS], git_name[:reponame])
      Dir.mkdir(prepare_path)
      lockf_path = File.join(prepare_path, "lock")
      File.open(lockf_path, File::RDWR | File::CREAT) do |f|
        f.flock(File::LOCK_EX)
        f.rewind
        #Начинаем сборку      
        @db.create_build_task(prj_id, git_id, build_path)
        build_id = @db.last_id    
        repo_path = File.join(proj_path, PROJECTS_STRUCTURE[:REPO])
        git_source = File.join(proj_path, PROJECTS_STRUCTURE[:SRC], git_name[:reponame])
        f.puts(build_id)
        f.flush
        proj_info = get_project(prj_id)
        tmp_bld = false
        tmp_bld = true if proj_info[:tmpstpbuild].to_i != 0
        build_script = get_project_custom_build(prj_id, git_id)
        mock = MockManager.new(prepare_path, get_project_config(prj_id), counter_file, @db, build_path, repo_path, 
        git_source, build_id, prep_script, spec_file, repo_lock, 
        git_id, tmp_bld, build_script)
        bld_id = build_id
        @db.update_build_task_error_log(build_id, mock.get_build_process_log)
        @db.update_build_task_status(build_id, 3)
        mock.build_task(build_lock)
      end
    end
    bld_id
  end

  def delete_git_from_project(prj_id, git_id)
    @error = nil
    builds_lst = db.get_builds_for_project_git(prj_id, git_id)
    active_build = false
    builds_lst.each do |item|
      if item[:state] == 0
        active_build = true
        break
      end
    end
    if active_build
      @error = "Нельзя удалить git репозиторий с незавершенными сборками"
    else
      proj_path = get_project_path(prj_id)
      git_name = @db.get_repo_info_by_id(git_id)
      git_source = File.join(proj_path, PROJECTS_STRUCTURE[:SRC], git_name[:reponame])
      FileUtils.rm_rf(git_source, secure: true)
      @db.delete_git_from_project(prj_id, git_id)
    end
    @error
  end

  def delete_project(prj_id)
    @error = nil
    builds_lst = db.get_builds_for_project(prj_id)
    active_build = false
    builds_lst.each do |item|
      if item[:state] == 0
        active_build = true
        break
      end
    end
    if active_build
      @error = "Нельзя удалить git репозиторий с незавершенными сборками"
    else
      linked = @db.projects_with_current_as_link(prj_id)
      if linked.nil? || linked.length == 0
        proj_path = get_project_path(prj_id)
        FileUtils.rm_rf(proj_path, secure: true)
        if @db.delete_project(prj_id) != 0 
          @error = "На текущий проект ссылаются другие проекты. Удаление запрещено"
        end
      else
        @error = "На текущий проект ссылаются другие проекты. Удаление запрещено"
      end
    end
    @error
  end

  def sign_project(prj_id, key_path, password, url, tpl_dir)
    @error = nil
    proj_path = get_project_path(prj_id)
    sign_repo_path = File.join(proj_path, PROJECTS_STRUCTURE[:SIGNED])
    repo_path = File.join(proj_path, PROJECTS_STRUCTURE[:REPO])
    repo_sign = RepoManager.new(sign_repo_path)
    repo_key = RepoManagerKeys.new(key_path)
    if password.nil?
      password = repo_key.check_password_exists
    end
    if password.nil?
      @error = "Не указан пароль для подписи"
    else
      repo_lock = File.join(proj_path, PROJECTS_STRUCTURE[:CONFIGS], ".repolock")
      sign_lock = File.join(proj_path, PROJECTS_STRUCTURE[:CONFIGS], ".signlock")
      prj = @db.proj(prj_id)
      if repo_key.check_key_exists
        File.open(sign_lock, File::RDWR | File::CREAT) do |s|
          s.flock(File::LOCK_EX)
          File.open(repo_lock, File::RDWR | File::CREAT) do |f|
            f.flock(File::LOCK_EX)
            rpm_list = get_rpms_list(repo_path)
            if prj[:public] == 0
              rpm_list = rpm_list.reject do |item|
                block = false
                block = true if item =~ /\.src\.rpm$/ || item =~ /SRPMS/ || item =~ /Debug/ || item =~ /(debuginfo.+rpm$)|(debugsource.+rpm$)/
                block
              end
            end
            rpm_signed_list = get_rpms_list(sign_repo_path)
            if rpm_signed_list.length > rpm_list.length 
              rpms_for_delete = get_rpms_list_full(sign_repo_path)
              rpms_for_delete.each do |item|
                File.unlink(item)
              end
            end
            rpm_list = rpm_list.select do |item|
              sign_repo_path_rpm = File.join(sign_repo_path, item)
              unless File.exist?(sign_repo_path_rpm)
                file_path_full = File.join(repo_path, item)
                unless File.exist?(File.dirname(sign_repo_path_rpm))
                  FileUtils.mkdir_p(File.dirname(sign_repo_path_rpm))
                end
                FileUtils.cp_r(file_path_full, File.dirname(sign_repo_path_rpm), verbose: false, remove_destination: false)
                sha256 = Digest::SHA256.file(file_path_full)
                rpm_info = @db.get_rpm_info_by_hash(sha256.hexdigest)
                unless rpm_info.nil?
                  @db.update_rpm_sign(rpm_info[:id], sign_repo_path_rpm)
                end
                repo_key.sign_package(sign_repo_path_rpm, password)
              end
            end
            repo_url = "http://localhost/"
            if prj[:remote_address].nil? || prj[:remote_address].strip == ""
              repo_url = url
            else
              repo_url = prj[:remote_address]
            end
            if repo_url[-1] != "/"
              repo_url = repo_url + "/"
            end
            repo_sign.repoview(repo_url, prj[:projname], tpl_dir)
            repo_sign.create_repo
            pub_key = repo_key.get_publick_key
            proj_repo_key = File.join(sign_repo_path, "#{prj[:projname]}-gpg-key")
            unless File.exist?(proj_repo_key)
              FileUtils.cp_r(pub_key, proj_repo_key, verbose: false, remove_destination: false)
            end
          end
        end
      else
        @error = "Ключ для подписи отсутствует"
      end
    end
    @error
  end

  def set_address(prj_id, address)
    @error = nil
    if address.nil?
      address = ""
    else
      address = address.strip
    end
    @db.set_project_address(prj_id, address)
    @error
  end

  def get_sign_path(id)
    path = get_project_path(id)
    File.join(path, PROJECTS_STRUCTURE[:SIGNED])
  end

  def recreate_repo(id)
    proj_path = get_project_path(id)
    repo_path = File.join(proj_path, PROJECTS_STRUCTURE[:REPO])
    repoman = RepoManager.new(repo_path)
    repoman.create_repo
  end

  def get_snap_list(id)
    proj_path = get_project_path(id)
    snap_path = File.join(proj_path, PROJECTS_STRUCTURE[:SNAP])
    
    unless Dir.exist?(snap_path)
      FileUtils.mkdir_p(snap_path)
    end

    snap_list = Dir.glob(File.join(snap_path, "*")).select { |path| File.directory?(path) }.map { |dir| File.basename(dir) }

    return snap_list
  end

  def create_snapshot(id)
    proj_path = get_project_path(id)
    snap_path = File.join(proj_path, PROJECTS_STRUCTURE[:SNAP])
    repo_path = File.join(proj_path, PROJECTS_STRUCTURE[:REPO])
    
    unless Dir.exist?(snap_path)
      FileUtils.mkdir_p(snap_path)
    end
    
    date_str = Time.now.strftime("%Y-%m-%d")
    snapshot_dir = "#{date_str}_0"

    while Dir.exist?(File.join(snap_path, snapshot_dir))
      num = snapshot_dir.split("_").last.to_i
      num += 1
      snapshot_dir = "#{date_str}_#{num}"
    end

    snapshot_full_path = File.join(snap_path, snapshot_dir)
    FileUtils.cp_r(repo_path, snapshot_full_path)
  end

  def delete_snapshot(id, snap)
    proj_path = get_project_path(id)
    snap_path = get_project_snap(id, snap)
    snap_path_base = File.join(proj_path, PROJECTS_STRUCTURE[:SNAP])
    unless snap_path == snap_path_base || "#{snap_path}/" == snap_path_base || snap_path == "#{snap_path_base}/"
      if File.exist?(snap_path)
        FileUtils.rm_rf(snap_path)
      end
    end
  end

  def restore_snapshot(id, snap)
    repo_path = get_project_repo(id)
    snap_path = get_project_snap(id, snap)
    
    if  Dir.exist?(repo_path) && Dir.exist?(snap_path)
      Dir.glob(File.join(repo_path, '*')).each { |file| File.unlink(file) if File.file?(file) || FileUtils.rm_rf(file) }
      FileUtils.cp_r(Dir.glob(File.join(snap_path, '*')), repo_path)
    end
  end

  def get_repo_dirs_list(id)
    repo_path = get_project_repo(id)
    dir_list = []
    if Dir.exist?(repo_path)
      dir_list = Dir.entries(repo_path).reject { |entry| entry == '.' || entry == '..' || entry == 'repodata' }
    end
    dir_list
  end

  def add_rpm(id, directory, rpm_file)
    repo_path = get_project_repo(id)
    if !Dir.exist?(repo_path)
      return "Репозиторий отсутствует"
    end

    target_dir = File.join(repo_path, directory)
    unless Dir.exist?(target_dir)
      Dir.mkdir(target_dir)
    end

    target_file = File.join(target_dir, File.basename(rpm_file[:filename]))
    if File.exist?(target_file)
      return "Файл #{File.basename(rpm_file[:filename])} уже существует"
    end

    begin
      File.open(target_file, "wb") do |file|
        file.write(rpm_file[:tempfile].read)
      end
    rescue => e
      return "Ошибка при создании файла: #{e.message}"
    end

    @db.add_custom_rpm_to_proj(id, File.basename(rpm_file[:filename]), target_file)

    nil
  end

  def get_project_uploaded_rpms(id)
    list = []
    rpm_list = @db.get_project_uploaded_rpms(id)
    rpm_list.each do |item|
      list << item if File.exist?(item[:rpm_path])
    end
    list
  end

  def get_project_custom_build(id, repo_id)
    data = {:script_name => nil, :content => nil, :description => nil, :created => nil }
    result = @db.get_project_custom_build(id, repo_id).first
    unless result.nil?
      data[:script_name]=result[:filepath]
      data[:content]=result[:content]
      data[:description]=result[:descr]
      data[:created]=result[:create_at]
    end
    data
  end

  def set_project_custom_build(id, repo_id, script_name, descr, content)
    @db.set_project_custom_build(id, repo_id, script_name, descr, content)
  end

  def del_project_custom_build(id, repo_id)
    @db.del_project_custom_build(id, repo_id)
  end
end
