/*---------------------------------------------------------------------\
|                          ____ _   __ __ ___                          |
|                         |__  / \ / / . \ . \                         |
|                           / / \ V /|  _/  _/                         |
|                          / /__ | | | | | |                           |
|                         /_____||_| |_| |_|                           |
|                                                                      |
\---------------------------------------------------------------------*/
#include "rpmmd.h"
#include <zypp/repo/RepoException.h>
#include <zypp-core/ng/ui/ProgressObserver>
#include <zypp-media/ng/ProvideSpec>
#include <zypp/ng/Context>


#include <zypp/ng/repo/workflows/repodownloaderwf.h>
#include <zypp/parser/yum/RepomdFileReader.h>
#include <zypp/repo/yum/RepomdFileCollector.h>
#include <zypp/ng/workflows/checksumwf.h>

#undef  ZYPP_BASE_LOGGER_LOGGROUP
#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::repomanager"

namespace zyppng::RpmmdWorkflows {

  namespace {

    using namespace zyppng::operators;

    struct StatusLogic {

    public:
      using MediaHandle     = typename Provide::MediaHandle;
      using ProvideRes      = typename Provide::Res;

      StatusLogic(  repo::DownloadContextRef ctx, MediaHandle &&media  )
        : _ctx(std::move(ctx))
        , _handle(std::move(media))
      {}

      MaybeAwaitable<expected<zypp::RepoStatus>> execute() {
        return _ctx->zyppContext()->provider()->provide( _handle, _ctx->repoInfo().path() / "/repodata/repomd.xml" , ProvideFileSpec().setMirrorsAllowed(false) )
          | [this]( expected<ProvideRes> repomdFile ) {

              if ( !repomdFile )
                return makeReadyTask( make_expected_success (zypp::RepoStatus() ));

              zypp::RepoStatus status ( repomdFile->file() );

              if ( !status.empty() && _ctx->repoInfo ().requireStatusWithMediaFile()) {
                return _ctx->zyppContext()->provider()->provide( _handle, "/media.1/media"  , ProvideFileSpec().setMirrorsAllowed(false) )
                  | [status = std::move(status)]( expected<ProvideRes> mediaFile ) mutable {
                      if ( mediaFile ) {
                        return make_expected_success( status && zypp::RepoStatus( mediaFile->file()) );
                      }
                      return make_expected_success( std::move(status) );
                    };
              }
              return makeReadyTask( make_expected_success(std::move(status)) );
            };
      }

       repo::DownloadContextRef _ctx;
      MediaHandle _handle;
    };
  }

  MaybeAwaitable<expected<zypp::RepoStatus> > repoStatus( repo::DownloadContextRef dl, ProvideMediaHandle mediaHandle)
  {
    StatusLogic impl( std::move(dl), std::move(mediaHandle) );
    zypp_co_return zypp_co_await( impl.execute() );
  }


  namespace {

    using namespace zyppng::operators;

    struct DlLogic : private zypp::repo::yum::RepomdFileCollector {

    public:
      using MediaHandle     = typename Provide::MediaHandle;
      using ProvideRes      = typename Provide::Res;

      DlLogic(  repo::DownloadContextRef ctx, MediaHandle &&mediaHandle, ProgressObserverRef &&progressObserver )
        : zypp::repo::yum::RepomdFileCollector( ctx->destDir() )
        , _ctx( std::move(ctx))
        , _mediaHandle(std::move(mediaHandle))
        , _progressObserver(std::move(progressObserver))
      {}

      auto execute() {
        // download media file here
        return RepoDownloaderWorkflow::downloadMediaInfo( _mediaHandle, _ctx->destDir() )
          | [this]( expected<zypp::ManagedFile> &&mediaInfo ) {

              // remember the media info if we had one
              if ( mediaInfo ) _ctx->files().push_back ( std::move(mediaInfo.get()) );

              if ( _progressObserver ) _progressObserver->inc();

              return RepoDownloaderWorkflow::downloadMasterIndex( _ctx, _mediaHandle, _ctx->repoInfo().path() / "/repodata/repomd.xml" )
                | inspect( incProgress( _progressObserver ) )
                | and_then( [this] (  repo::DownloadContextRef && ) {

                    zypp::Pathname repomdPath = _ctx->files().front();
                    std::vector<zypp::OnMediaLocation> requiredFiles;
                    try {
                      zypp::parser::yum::RepomdFileReader reader( repomdPath, [this]( const zypp::OnMediaLocation & loc_r, const std::string & typestr_r ){ return collect( loc_r, typestr_r ); });
                      finalize([&]( const zypp::OnMediaLocation &file ){
                        if ( file.medianr () != 1 ) {
                          // ALL repo files NEED to come from media nr 1 , otherwise we fail
                          ZYPP_THROW(zypp::repo::RepoException( _ctx->repoInfo(), "Repo can only require metadata files from primary medium."));
                        }
                        requiredFiles.push_back( file );
                      });
                    } catch ( ... ) {
                      return makeReadyTask(expected< repo::DownloadContextRef>::error( ZYPP_FWD_CURRENT_EXCPT() ) );
                    }

                    // add the required files to the base steps
                    if ( _progressObserver ) _progressObserver->setBaseSteps ( _progressObserver->baseSteps () + requiredFiles.size() );

                    return transform_collect  ( std::move(requiredFiles), [this]( zypp::OnMediaLocation file ) {

                      return DownloadWorkflow::provideToCacheDir( _ctx, _mediaHandle, file.filename(), ProvideFileSpec(file) )
                          | inspect ( incProgress( _progressObserver ) );

                    }) | and_then ( [this]( std::vector<zypp::ManagedFile> &&dlFiles ) {
                      auto &downloadedFiles = _ctx->files();
                      downloadedFiles.insert( downloadedFiles.end(), std::make_move_iterator(dlFiles.begin()), std::make_move_iterator(dlFiles.end()) );
                      return expected< repo::DownloadContextRef>::success( std::move(_ctx) );
                    });
                  });

          } | finishProgress( _progressObserver );
      }

    private:

      const zypp::RepoInfo &repoInfo() const override {
        return _ctx->repoInfo();
      }

      const zypp::filesystem::Pathname &deltaDir() const override {
        return _ctx->deltaDir();
      }

       repo::DownloadContextRef _ctx;
      MediaHandle _mediaHandle;
      ProgressObserverRef _progressObserver;
    };
  }

  MaybeAwaitable<expected< repo::DownloadContextRef> > download( repo::DownloadContextRef dl, ProvideMediaHandle mediaHandle, ProgressObserverRef progressObserver)
  {
    DlLogic impl( std::move(dl), std::move(mediaHandle), std::move(progressObserver) );
    zypp_co_return zypp_co_await( impl.execute() );
  }


}
