use std::{
  env::args,
  fs::{read, File},
  io::Cursor,
  sync::{Arc, LazyLock},
  time::Duration,
};

use tokio::{
  io::BufReader,
  sync::{mpsc, oneshot, OnceCell},
};
use tracing::{error, info, info_span, trace, warn, Instrument, Span};
use tracing_subscriber::{filter::FilterFn, layer::SubscriberExt, util::SubscriberInitExt, Layer};
use webrtc::{
  api::{
    interceptor_registry::register_default_interceptors,
    media_engine::{MediaEngine, MIME_TYPE_OPUS},
    setting_engine::SettingEngine,
    APIBuilder, API,
  },
  data_channel::data_channel_init::RTCDataChannelInit,
  ice_transport::{
    ice_candidate::{RTCIceCandidate, RTCIceCandidateInit},
    ice_connection_state::RTCIceConnectionState,
    ice_server::RTCIceServer,
  },
  interceptor::registry::Registry,
  media::{
    io::{ogg_reader::OggReader, ogg_writer::OggWriter, Writer},
    Sample,
  },
  peer_connection::{
    configuration::RTCConfiguration,
    offer_answer_options::{RTCAnswerOptions, RTCOfferOptions},
    peer_connection_state::RTCPeerConnectionState,
    sdp::session_description::RTCSessionDescription,
    RTCPeerConnection,
  },
  rtp_transceiver::{
    rtp_codec::{RTCRtpCodecCapability, RTCRtpCodecParameters, RTPCodecType},
    rtp_transceiver_direction::RTCRtpTransceiverDirection,
    RTCRtpTransceiverInit,
  },
  track::track_local::{track_local_static_sample::TrackLocalStaticSample, TrackLocal},
};
use xtra::{Actor, Address, Handler, Mailbox};
const CODEC: LazyLock<RTCRtpCodecParameters> = LazyLock::new(|| RTCRtpCodecParameters {
  capability: RTCRtpCodecCapability {
    mime_type: MIME_TYPE_OPUS.to_owned(),
    clock_rate: 48000,
    channels: 2,
    sdp_fmtp_line: "".to_owned(),
    rtcp_feedback: vec![],
  },
  payload_type: 111,
  ..Default::default()
});
#[tokio::main]
async fn main() -> anyhow::Result<()> {
  tracing_subscriber::registry()
    .with(tracing_subscriber::fmt::layer().with_filter(FilterFn::new(|m| {
      m.module_path().map(|path| !path.contains("webrtc_ice") && !path.contains("webrtc_mdns")).unwrap_or_default()
      // m.module_path().map(|path| path.contains("proximity_client")).unwrap_or_default()
      // true
    })))
    .init();
  let mut m = MediaEngine::default();

  m.register_codec(CODEC.clone(), RTPCodecType::Audio)?;
  let mut registry = Registry::new();
  registry = register_default_interceptors(registry, &mut m)?;
  let api = Arc::new(
    APIBuilder::new()
      .with_media_engine(m)
      // .with_setting_engine({
      //   let mut engine = SettingEngine::default();
      //   // engine.enable_sender_rtx(is_enabled);
      //   engine
      // })
      .with_interceptor_registry(registry)
      .build(),
  );
  let config = RTCConfiguration {
    ice_servers: vec![RTCIceServer {
      urls: vec!["stun:stun.l.google.com:19302".to_owned()],
      ..Default::default()
    }],
    ..Default::default()
  };

  let sender = Sender::spawn(api.clone(), config.clone());

  Receiver::run(api, config, sender).instrument(info_span!("receiver")).await;
  Ok(())
}

#[derive(Actor)]
struct Sender {
  peer_connection: Arc<RTCPeerConnection>,
  receiver: OnceCell<Address<Receiver>>,
  audio_track: Arc<TrackLocalStaticSample>,
}
#[derive(Actor)]
struct Receiver {
  peer_connection: Arc<RTCPeerConnection>,
  sender: Address<Sender>,
}

impl Sender {
  pub fn spawn(api: Arc<API>, config: RTCConfiguration) -> Address<Sender> {
    let (address, mailbox) = Mailbox::unbounded();
    let span = info_span!("sender");
    tokio::spawn(
      async move {
        let address = mailbox.address();
        let peer_connection = Arc::new(api.new_peer_connection(config).await.expect("rraaahaa"));
        // Set the handler for ICE connection state
        // This will notify you when the peer has connected/disconnected
        peer_connection.on_ice_connection_state_change({
          let address = address.clone();
          Box::new(move |connection_state: RTCIceConnectionState| {
            info!("Connection State has changed {connection_state}");
            if connection_state == RTCIceConnectionState::Connected {
              let address = address.clone();
              tokio::spawn(async move {
                address.send(Connected).await.unwrap();
              });
            }
            Box::pin(async {})
          })
        });

        // Set the handler for Peer connection state
        // This will notify you when the peer has connected/disconnected
        peer_connection.on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| {
          info!("Peer Connection State has changed: {s}");

          if s == RTCPeerConnectionState::Failed {
            // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
            // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
            // Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
            info!("Peer Connection has gone to failed exiting");
          }

          Box::pin(async {})
        }));

        peer_connection.on_ice_candidate({
          let address = address.clone();
          let span = Span::current();
          Box::new(move |candidate: Option<RTCIceCandidate>| {
            let sender = address.clone();
            let span = span.clone();
            Box::pin(
              async move {
                if let Some(candidate) = candidate {
                  let candidate = candidate.to_json().unwrap();
                  warn!("candidate: {:?}", candidate);
                  sender.send((IceNotify, candidate)).await.unwrap();
                } else {
                  warn!("candidates drained");
                }
              }
              .instrument(span),
            )
          })
        });

        peer_connection.on_track(Box::new(|track, _, _| {
          warn!("aaaaaa");

          panic!()
        }));

        let audio_track = Arc::new(TrackLocalStaticSample::new(
          CODEC.capability.clone(),
          "audio".to_owned(),
          "webrtc-rs".to_owned(),
        ));
        let rtp_sender = peer_connection
          .add_transceiver_from_track(
            Arc::clone(&audio_track) as Arc<dyn TrackLocal + Send + Sync>,
            Some(RTCRtpTransceiverInit {
              direction: RTCRtpTransceiverDirection::Sendonly,
              send_encodings: Vec::new(),
            }),
          )
          .await
          .expect("oops");
        let rtp_sender = rtp_sender.sender().await;

        // Read incoming RTCP packets
        // Before these packets are returned they are processed by interceptors. For things
        // like NACK this needs to be called.
        tokio::spawn(async move {
          let mut rtcp_buf = vec![0u8; 1500];
          while let Ok((_, _)) = rtp_sender.read(&mut rtcp_buf).await {}
          error!("rtcp...");
        });

        xtra::run(
          mailbox,
          Sender {
            peer_connection,
            receiver: OnceCell::const_new(),
            audio_track,
          },
        )
        .await;

        info!("exited");
      }
      .instrument(span),
    );

    address
  }
}

impl Receiver {
  pub async fn run(api: Arc<API>, config: RTCConfiguration, sender: Address<Sender>) {
    let (address, mailbox) = Mailbox::unbounded();
    let peer_connection = Arc::new(api.new_peer_connection(config).await.expect("rraaahaa"));
    // Set the handler for ICE connection state
    // This will notify you when the peer has connected/disconnected
    peer_connection.on_ice_connection_state_change({
      let address = address.clone();
      Box::new(move |connection_state: RTCIceConnectionState| {
        info!("Connection State has changed {connection_state}");
        let address = address.clone();
        Box::pin(async move {
          if connection_state == RTCIceConnectionState::Connected {
            let _ = address.send(Connected).await.unwrap();
          }
        })
      })
    });

    // Set the handler for Peer connection state
    // This will notify you when the peer has connected/disconnected
    peer_connection.on_peer_connection_state_change(Box::new(move |s: RTCPeerConnectionState| {
      info!("Peer Connection State has changed: {s}");

      if s == RTCPeerConnectionState::Failed {
        // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
        // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
        // Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
        error!("Peer Connection has gone to failed exiting");
      }

      Box::pin(async {})
    }));

    peer_connection.on_track(Box::new(move |track, track_receiver, transceiver| {
      info!("holy track");

      Box::pin(async move {
        let mut file = File::create("out.opus").unwrap();
        let mut samplero = OggWriter::new(&mut file, 48000, 2).unwrap();

        while let Ok((packet, _)) = track.read_rtp().await {
          samplero.write_rtp(&packet).unwrap();
        }
        println!("done");
        std::process::exit(1);
      })
    }));
    peer_connection.on_ice_candidate({
      let sender = sender.clone();
      let span = Span::current();
      Box::new(move |candidate| {
        let sender = sender.clone();
        let span = span.clone();
        Box::pin(
          async move {
            if let Some(candidate) = candidate {
              let candidate = candidate.to_json().unwrap();
              // warn!("candidate: {:?}", candidate);
              sender.send(candidate).await.unwrap();
            } else {
              // warn!("candidates drained");
            }
          }
          .instrument(span),
        )
      })
    });

    let transceiver = peer_connection
      .add_transceiver_from_kind(
        RTPCodecType::Audio,
        Some(RTCRtpTransceiverInit {
          direction: RTCRtpTransceiverDirection::Recvonly,
          send_encodings: vec![],
        }),
      )
      .await
      .unwrap();
    let rtp_sender = transceiver.sender().await;
    tokio::spawn(async move {
      let mut rtcp_buf = vec![0u8; 1500];
      while let Ok((packet, _)) = rtp_sender.read(&mut rtcp_buf).await {}
      error!("rtcp...");
    });

    let offer = sender.send(address).await.unwrap();
    error!("offer {offer:?}");
    peer_connection.set_remote_description(offer).await.unwrap();
    let answer = peer_connection.create_answer(None).await.unwrap();
    peer_connection.set_local_description(answer.clone()).await.unwrap();

    sender.send(answer).await.unwrap();

    xtra::run(
      mailbox,
      Receiver {
        peer_connection,
        sender,
      },
    )
    .await;
  }
}

impl Handler<Address<Receiver>> for Sender {
  type Return = RTCSessionDescription;

  async fn handle(&mut self, address: Address<Receiver>, ctx: &mut xtra::Context<Self>) -> Self::Return {
    self.receiver.set(address.clone()).unwrap();
    self.peer_connection.on_ice_candidate(Box::new(move |candidate| {
      let address = address.clone();
      Box::pin(async move {
        if let Some(candidate) = candidate {
          address.send(candidate.to_json().unwrap()).await.unwrap();
        }
      })
    }));
    let offer = self.peer_connection.create_offer(None).await.unwrap();
    self.peer_connection.set_local_description(offer.clone()).await.unwrap();

    offer
  }
}

impl Handler<RTCSessionDescription> for Sender {
  type Return = ();

  async fn handle(&mut self, message: RTCSessionDescription, ctx: &mut xtra::Context<Self>) -> Self::Return {
    self.peer_connection.set_remote_description(message).await.unwrap();
  }
}

struct IceNotify;
impl Handler<(IceNotify, RTCIceCandidateInit)> for Sender {
  type Return = ();

  async fn handle(
    &mut self,
    (_, message): (IceNotify, RTCIceCandidateInit),
    _: &mut xtra::Context<Self>,
  ) -> Self::Return {
    self.receiver.get().unwrap().send(message).await.unwrap()
  }
}
impl Handler<RTCIceCandidateInit> for Sender {
  type Return = ();

  async fn handle(&mut self, message: RTCIceCandidateInit, _: &mut xtra::Context<Self>) -> Self::Return {
    // trace!("adding ice candidate {message:?}");
    self.peer_connection.add_ice_candidate(message).await.unwrap();
  }
}

impl Handler<RTCIceCandidateInit> for Receiver {
  type Return = ();

  async fn handle(&mut self, message: RTCIceCandidateInit, _: &mut xtra::Context<Self>) -> Self::Return {
    // trace!("adding ice candidate {message:?}");
    self.peer_connection.add_ice_candidate(message).await.unwrap();
  }
}

pub struct Connected;
impl Handler<Connected> for Sender {
  type Return = ();

  async fn handle(&mut self, _: Connected, ctx: &mut xtra::Context<Self>) -> Self::Return {
    info!("connected");
    let address = ctx.mailbox().address();

    tokio::time::sleep(Duration::from_secs(1)).await;

    let file = read(args().nth(1).unwrap()).unwrap();
    let (mut ogg, _) = OggReader::new(Cursor::new(file), true).unwrap();
    const OGG_PAGE_DURATION: Duration = Duration::from_millis(20);
    let mut ticker = tokio::time::interval(OGG_PAGE_DURATION);
    let audio_track = self.audio_track.clone();

    tokio::spawn(
      async move {
        // Keep track of last granule, the difference is the amount of samples in the buffer
        let mut last_granule: u64 = 0;
        while let Ok((page_data, page_header)) = ogg.parse_next_page() {
          // The amount of samples is the difference between the last and current timestamp
          let sample_count = page_header.granule_position - last_granule;
          last_granule = page_header.granule_position;
          let sample_duration = Duration::from_millis(sample_count * 1000 / 48000);

          info!("writing sample");
          audio_track
            .write_sample(&Sample {
              data: page_data.freeze(),
              duration: sample_duration,
              ..Default::default()
            })
            .await
            .unwrap();

          let _ = ticker.tick().await;
        }
        info!("done streaming audio");
        address.send(Stop).await.unwrap();
      }
      .in_current_span(),
    );
  }
}

struct Stop;
impl Handler<Stop> for Sender {
  type Return = ();
  async fn handle(&mut self, _: Stop, ctx: &mut xtra::Context<Self>) -> Self::Return {
    self.peer_connection.close().await.unwrap();
    ctx.stop_self();
  }
}

impl Handler<Connected> for Receiver {
  type Return = ();

  async fn handle(&mut self, _: Connected, _: &mut xtra::Context<Self>) -> Self::Return {
    info!("connected!");
  }
}
