#pragma once

#include "logger.hpp"
#include <QAudioOutput>
#include <QMediaPlayer>
#include <QObject>
#include <QUrl>
#include <algorithm>
#include <qcoreapplication.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qurl.h>
#include <string>
#include <vector>

class PlaylistController : public QObject {
  Q_OBJECT
  Q_PROPERTY(QObject *videoOutputA READ videoOutputA WRITE setVideoOutputA
                 NOTIFY videoOutputChangedA)

  Q_PROPERTY(QObject *audioOutputA READ audioOutputA WRITE setAudioOutputA
                 NOTIFY audioOutputChangedA)

  Q_PROPERTY(QObject *videoOutputB READ videoOutputB WRITE setVideoOutputB
                 NOTIFY videoOutputChanged)

  Q_PROPERTY(QObject *audioOutputB READ audioOutputB WRITE setAudioOutputB
                 NOTIFY audioOutputChangedB)

  Q_PROPERTY(QString playingState READ playingState NOTIFY playingStateChanged)

public:
  explicit PlaylistController(QObject *parent = nullptr) : QObject(parent) {
    m_playerA = new QMediaPlayer(this);
    m_playerB = new QMediaPlayer(this);

    m_activePlayer = m_playerA;
    m_nextPlayer = m_playerB;

    connect(m_playerA, &QMediaPlayer::mediaStatusChanged, this,
            [this](QMediaPlayer::MediaStatus status) {
              onMediaStatusChanged(m_playerA, status);
            });

    connect(m_playerB, &QMediaPlayer::mediaStatusChanged, this,
            [this](QMediaPlayer::MediaStatus status) {
              onMediaStatusChanged(m_playerB, status);
            });
  }

  QObject *videoOutputA() const { return m_videoOutputA; }
  QObject *videoOutputB() const { return m_videoOutputB; }
  QObject *audioOutputA() const { return m_audioOutputA; }
  QObject *audioOutputB() const { return m_audioOutputB; }
  QString playingState() const { return m_playingState ? "playing" : "pause"; }

  void setVideoOutputA(QObject *output) {
    if (m_videoOutputA != output) {
      m_videoOutputA = output;

      if (m_videoOutputA) {
        m_playerA->setVideoOutput(m_videoOutputA);
      }

      emit videoOutputChangedA();
    }
  }

  void setAudioOutputA(QObject *output) {
    if (m_audioOutputA != output) {
      m_audioOutputA = output;

      if (m_videoOutputA) {
        if (auto *audio = qobject_cast<QAudioOutput *>(m_audioOutputA)) {
          m_playerA->setAudioOutput(audio);
        }
      }

      emit audioOutputChangedA();
    }
  }

  void setVideoOutputB(QObject *output) {
    if (m_videoOutputB != output) {
      m_videoOutputB = output;

      if (m_videoOutputB) {
        m_playerB->setVideoOutput(m_videoOutputB);
      }

      emit videoOutputChangedB();
    }
  }

  void setAudioOutputB(QObject *output) {
    if (m_audioOutputB != output) {
      m_audioOutputB = output;

      if (m_videoOutputB) {
        if (auto *audio = qobject_cast<QAudioOutput *>(m_audioOutputB)) {
          m_playerB->setAudioOutput(audio);
        }
      }

      emit audioOutputChangedB();
    }
  }

  Q_INVOKABLE void prepareNext(const QUrl &url) {
    // Desplegar hilo que descargue y gestione el mediaplayer

    Logger::info("Loading: " + url.toString().toStdString());
    m_nextPlayer->setSource(url);

    // Para que se quede listo
    m_nextPlayer->play();
    m_nextPlayer->pause();
    m_nextPlayer->setPosition(0);
  }

  Q_INVOKABLE void swapAndPlay() {
    m_activePlayer->stop();

    std::swap(m_activePlayer, m_nextPlayer);

    m_activePlayer->play();
    m_playingState = true;
    // m_activePlayer->setPlaybackRate(1);

    if (m_queue.size() - 1 > m_queue_index) {
      prepareNext(QUrl::fromLocalFile(m_queue.at(m_queue_index + 1)));
    }
  }

  Q_INVOKABLE void setVolume(float volume) {
    if (auto *audio = qobject_cast<QAudioOutput *>(m_audioOutputA)) {
      audio->setVolume(std::clamp(m_volume + volume, 0.0f, 1.0f));
    }

    if (auto *audio = qobject_cast<QAudioOutput *>(m_audioOutputB)) {
      audio->setVolume(std::clamp(m_volume + volume, 0.0f, 1.0f));
    }
  }

  Q_INVOKABLE void seek(long long time) {
    m_activePlayer->setPosition(std::clamp(m_activePlayer->position() + time,
                                           0ll, m_activePlayer->duration()));
  }

  Q_INVOKABLE void addToQueue(const QString &url) {
    int n_ele = m_queue.size();

    m_queue.push_back(url);
    
    if (!m_playing) {
      m_playing = true;
      prepareNext(QUrl::fromLocalFile(m_queue.at(m_queue_index)));
    }
  }

  Q_INVOKABLE void togglePause() {
    m_playingState = !m_playingState;
    if (!m_playingState)
      m_activePlayer->pause();
    else
      m_activePlayer->play();

    emit playingStateChanged();
  }

signals:
  void videoOutputChanged();
  void videoOutputChangedA();
  void audioOutputChangedA();
  void videoOutputChangedB();
  void audioOutputChangedB();
  void playingStateChanged();

private:
  std::vector<QString> m_queue = std::vector<QString>();
  bool m_playing = false;
  QMediaPlayer *m_playerA{nullptr};
  QMediaPlayer *m_playerB{nullptr};
  QMediaPlayer *m_activePlayer{nullptr};
  QMediaPlayer *m_nextPlayer{nullptr};
  QObject *m_videoOutputA{nullptr};
  QObject *m_videoOutputB{nullptr};
  QObject *m_audioOutputA{nullptr};
  QObject *m_audioOutputB{nullptr};
  float m_volume = 1;
  bool m_playingState = false;

  int m_queue_index = 0;

  void onMediaStatusChanged(QMediaPlayer *player,
                            QMediaPlayer::MediaStatus status) {
    bool isActive = (player == m_activePlayer);

    switch (status) {
    case QMediaPlayer::LoadedMedia:
      Logger::info(isActive ? "Active media loaded"
                            : "Next media loaded and ready");
      break;

    case QMediaPlayer::BufferedMedia:
      Logger::info("Media fully buffered");
      break;

    case QMediaPlayer::EndOfMedia:
      if (isActive) {
        if (m_queue.size() - 1 > m_queue_index) {
          m_queue_index++;
          swapAndPlay();
        }
        else {
          m_playing = false;
        }
      }
      break;

    case QMediaPlayer::InvalidMedia:
      Logger::error("Failed to load or play media");
      break;

    default:
      break;
    }
  }
};
