#![cfg_attr(docsrs, feature(doc_cfg))]
#![feature(type_alias_impl_trait)]

use std::{net::IpAddr, sync::Arc};

use anyhow::Result;
use fred::{interfaces::StreamsInterface, types::streams::XID};
use ipbin::ipbin;
use kvid::KvId;
use linkme::DistributedSlice;
use tosql::ToSqlTrait;
use xkv::R;

pub static SITE_LOG_ID: KvId = KvId::new("siteLog");

pub type JoinHandle = tokio::task::JoinHandle<Result<()>>;
pub type Task<T> = fn(Arc<Ctx>, Arc<T>) -> JoinHandle;
pub type HookLi<T> = [Task<T>];
pub type HookSlice<T> = &'static DistributedSlice<HookLi<T>>;

pub struct Ctx {
  pub ip: IpAddr,
  pub user_id: u64,
  pub site_id: u64,
  pub device_id: u64,
  pub bin: Vec<u8>,
  pub ipbin: Vec<u8>,
  pub site_key: Vec<u8>,
}

impl Ctx {
  pub fn new(ip: IpAddr, user_id: u64, site_id: u64, device_id: u64) -> Self {
    let site_key = xbin::concat!(b"site:", intbin::to_bin(site_id));
    Self {
      ip,
      user_id,
      site_id,
      device_id,
      site_key,
      ipbin: ipbin(ip),
      bin: vb::e_li([user_id, device_id]),
    }
  }
}

pub trait SiteLog: ToSqlTrait
where
  Self: 'static,
{
  const TYPE_ID: u64;
  const HOOK: HookSlice<Self>;

  fn site_log(self, ctx: Arc<Ctx>) -> impl Future<Output = Result<()>>
  where
    Self: Sized + Send,
  {
    tokio::spawn({
      let dump = self.dump();
      let ctx = ctx.clone();
      async move {
        xerr::log!(
          R.xadd::<(), _, _, _, _>(
            &ctx.site_key[..],
            false,
            None,
            XID::Auto,
            (&ctx.ipbin[..], xbin::concat!(ctx.bin, dump)),
          )
          .await
        );
      }
    });
    async move {
      let me = Arc::new(self);
      for i in Self::HOOK.iter().map(|f| f(ctx.clone(), me.clone())) {
        xerr::log!(i.await);
      }
      Ok(())
    }
  }
}
