use {
    crate::{
        async_engine::{AsyncEngine, SpawnedFuture},
        io_uring::{IoUring, IoUringError},
        pipewire::{
            pw_formatter::{PwFormatter, format},
            pw_ifs::{
                pw_client::{PwClient, PwClientMethods},
                pw_client_node::{
                    PW_CLIENT_NODE_FACTORY, PW_CLIENT_NODE_INTERFACE, PW_CLIENT_NODE_VERSION,
                    PwClientNode,
                },
                pw_core::{PW_CORE_VERSION, PwCore, PwCoreMethods},
                pw_registry::{PW_REGISTRY_VERSION, PwRegistry},
            },
            pw_mem::PwMemPool,
            pw_object::{PwObject, PwObjectData, PwObjectError, PwOpcode},
            pw_parser::{PwParser, PwParserError},
        },
        utils::{
            bitfield::Bitfield,
            bufio::{BufIo, BufIoError, BufIoIncoming, BufIoMessage},
            clonecell::CloneCell,
            copyhashmap::CopyHashMap,
            errorfmt::ErrorFmt,
            hash_map_ext::HashMapExt,
            numcell::NumCell,
            oserror::{OsError, OsErrorExt2},
            xrd::xrd,
        },
    },
    std::{
        cell::{Cell, RefCell},
        fmt::Display,
        io::Write,
        rc::{Rc, Weak},
    },
    thiserror::Error,
    uapi::{OwnedFd, c},
};

#[derive(Debug, Error)]
pub enum PwConError {
    #[error("Could not create a unix socket")]
    CreateSocket(#[source] OsError),
    #[error("Could not connect to the pipewire daemon")]
    ConnectSocket(#[source] IoUringError),
    #[error(transparent)]
    BufIoError(#[from] BufIoError),
    #[error("Server did not sent a required fd")]
    MissingFd,
    #[error("XDG_RUNTIME_DIR is not set")]
    XrdNotSet,
    #[error(transparent)]
    PwObjectError(#[from] PwObjectError),
    #[error(transparent)]
    PwParserError(#[from] PwParserError),
}

pub struct PwConHolder {
    pub con: Rc<PwCon>,
    outgoing: Cell<Option<SpawnedFuture<()>>>,
    incoming: Cell<Option<SpawnedFuture<()>>>,
}

pub struct PwCon {
    send_seq: NumCell<u32>,
    pub io: Rc<BufIo>,
    holder: CloneCell<Weak<PwConHolder>>,
    dead: Cell<bool>,
    pub objects: CopyHashMap<u32, Rc<dyn PwObject>>,
    pub ids: RefCell<Bitfield>,
    pub mem: PwMemPool,
    pub ring: Rc<IoUring>,
    pub eng: Rc<AsyncEngine>,
    pub owner: CloneCell<Option<Rc<dyn PwConOwner>>>,

    registry_generation: Cell<u64>,
    ack_registry_generation: Cell<u64>,
}

pub trait PwConOwner {
    fn killed(&self) {}
}

impl PwCon {
    pub fn create_client_node(self: &Rc<Self>, props: &[(String, String)]) -> Rc<PwClientNode> {
        let node = Rc::new(PwClientNode {
            data: self.proxy_data(),
            con: self.clone(),
            ios: Default::default(),
            owner: CloneCell::new(None),
            ports: Default::default(),
            port_out_free: RefCell::new(Default::default()),
            port_in_free: RefCell::new(Default::default()),
            activation: Default::default(),
            transport_in: Cell::new(None),
            transport_out: Default::default(),
            activations: Default::default(),
        });
        if !self.dead.get() {
            self.objects.set(node.data.id, node.clone());
        }
        self.create_object(
            PW_CLIENT_NODE_FACTORY,
            PW_CLIENT_NODE_INTERFACE,
            PW_CLIENT_NODE_VERSION,
            props,
            node.data.id,
        );
        node.send_update();
        node
    }

    pub fn destroy_obj(&self, obj: &impl PwObject) {
        obj.break_loops();
        self.send2(0, "core", PwCoreMethods::Destroy, |f| {
            f.write_struct(|f| {
                f.write_uint(obj.data().id);
            });
        });
        self.objects.remove(&obj.data().id);
    }

    pub fn kill(&self) {
        for obj in self.objects.lock().drain_values() {
            obj.break_loops();
        }
        self.io.shutdown();
        self.dead.set(true);
        if let Some(con) = self.holder.get().upgrade() {
            con.outgoing.take();
            con.incoming.take();
        }
        if let Some(owner) = self.owner.take() {
            owner.killed();
        }
    }

    pub fn id(&self) -> u32 {
        self.ids.borrow_mut().acquire()
    }

    pub fn proxy_data(&self) -> PwObjectData {
        PwObjectData {
            id: self.id(),
            bound_id: Cell::new(None),
            sync_id: Default::default(),
        }
    }

    pub fn send<P, O, F>(&self, proxy: &P, opcode: O, f: F)
    where
        P: PwObject,
        O: PwOpcode,
        F: FnOnce(&mut PwFormatter),
    {
        self.send2(proxy.data().id, proxy.interface(), opcode, f);
    }

    pub fn send2<O, F>(&self, id: u32, interface: &str, opcode: O, f: F)
    where
        O: PwOpcode,
        F: FnOnce(&mut PwFormatter),
    {
        if self.dead.get() {
            return;
        }
        let mut buf = self.io.buf();
        let mut fds = vec![];
        format(
            &mut buf,
            &mut fds,
            id,
            opcode.id(),
            self.send_seq.fetch_add(1),
            |fmt| {
                f(fmt);
                if self.ack_registry_generation.get() != self.registry_generation.get() {
                    let generation = self.registry_generation.get();
                    fmt.write_struct(|f| {
                        f.write_id(FOOTER_REGISTRY_GENERATION);
                        f.write_struct(|f| {
                            f.write_ulong(generation);
                        });
                    });
                    self.ack_registry_generation.set(generation);
                }
            },
        );
        if log::log_enabled!(log::Level::Trace) {
            log::trace!("CALL {}@{}: `{:?}`:", interface, id, opcode);
            let mut parser = PwParser::new(&buf[16..buf.len()], &fds);
            while parser.len() > 0 {
                log::trace!("{:#?}", parser.read_pod().unwrap());
            }
        }
        self.io.send(BufIoMessage {
            fds,
            buf: buf.unwrap(),
        });
    }

    #[expect(dead_code)]
    pub fn sync<P: PwObject>(&self, p: &P) {
        let seq = p.data().sync_id.fetch_add(1) + 1;
        self.send2(0, "core", PwCoreMethods::Sync, |f| {
            f.write_struct(|f| {
                f.write_uint(p.data().id);
                f.write_uint(seq);
            });
        });
    }

    pub fn send_hello(&self) {
        self.send2(0, "core", PwCoreMethods::Hello, |f| {
            f.write_struct(|f| f.write_int(PW_CORE_VERSION));
        });
    }

    #[expect(dead_code)]
    pub fn get_registry(self: &Rc<Self>) -> Rc<PwRegistry> {
        let registry = Rc::new(PwRegistry {
            data: self.proxy_data(),
            _con: self.clone(),
        });
        if !self.dead.get() {
            self.objects.set(registry.data.id, registry.clone());
        }
        self.send2(0, "core", PwCoreMethods::GetRegistry, |f| {
            f.write_struct(|f| {
                f.write_int(PW_REGISTRY_VERSION);
                f.write_uint(registry.data.id);
            });
        });
        registry
    }

    pub fn create_object(
        &self,
        factory: &str,
        ty: &str,
        version: i32,
        props: &[(String, String)],
        new_id: u32,
    ) {
        self.send2(0, "core", PwCoreMethods::CreateObject, |f| {
            f.write_struct(|f| {
                f.write_string(factory);
                f.write_string(ty);
                f.write_int(version);
                f.write_struct(|f| {
                    f.write_int(props.len() as _);
                    for (key, val) in props {
                        f.write_string(key);
                        f.write_string(val);
                    }
                });
                f.write_uint(new_id);
            });
        });
    }

    pub fn send_properties(&self) {
        self.send2(1, "client", PwClientMethods::UpdateProperties, |f| {
            f.write_struct(|f| {
                f.write_struct(|f| {
                    f.write_int(1);
                    f.write_string("application.name");
                    f.write_string("jay-portal");
                });
            });
        });
    }

    async fn handle_outgoing(self: Rc<Self>) {
        if let Err(e) = self.io.clone().outgoing().await {
            log::error!("{}", ErrorFmt(e));
        }
        self.kill();
    }

    async fn handle_incoming(self: Rc<Self>) {
        let incoming = Incoming {
            incoming: self.io.clone().incoming(),
            con: self.clone(),
            buf: vec![],
            fds: vec![],
        };
        incoming.run().await;
    }
}

impl Drop for PwConHolder {
    fn drop(&mut self) {
        self.con.owner.take();
        self.con.kill();
    }
}

impl PwConHolder {
    pub async fn new(eng: &Rc<AsyncEngine>, ring: &Rc<IoUring>) -> Result<Rc<Self>, PwConError> {
        let fd = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
            .map(Rc::new)
            .map_os_err(PwConError::CreateSocket)?;
        let mut addr = c::sockaddr_un {
            sun_family: c::AF_UNIX as _,
            ..uapi::pod_zeroed()
        };
        let xrd = match xrd() {
            Some(xrd) => xrd,
            _ => return Err(PwConError::XrdNotSet),
        };
        {
            let mut path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
            let _ = write!(path, "{}/pipewire-0", xrd);
        }
        if let Err(e) = ring.connect(&fd, &addr).await {
            return Err(PwConError::ConnectSocket(e));
        }
        let io = Rc::new(BufIo::new(&fd, ring));
        let data = Rc::new(PwCon {
            send_seq: Default::default(),
            io,
            holder: Default::default(),
            dead: Cell::new(false),
            objects: Default::default(),
            ids: Default::default(),
            mem: Default::default(),
            ring: ring.clone(),
            eng: eng.clone(),
            owner: Default::default(),
            registry_generation: Cell::new(0),
            ack_registry_generation: Cell::new(0),
        });
        let core = Rc::new(PwCore {
            data: data.proxy_data(),
            con: data.clone(),
        });
        let client = Rc::new(PwClient {
            data: data.proxy_data(),
            _con: data.clone(),
        });
        data.objects.set(0, core.clone());
        data.objects.set(1, client.clone());
        data.send_hello();
        data.send_properties();
        let con = Rc::new(PwConHolder {
            outgoing: Cell::new(Some(
                eng.spawn("pw outgoing", data.clone().handle_outgoing()),
            )),
            incoming: Cell::new(Some(
                eng.spawn("pw incoming", data.clone().handle_incoming()),
            )),
            con: data,
        });
        con.con.holder.set(Rc::downgrade(&con));
        Ok(con)
    }
}

struct Incoming {
    con: Rc<PwCon>,
    incoming: BufIoIncoming,
    buf: Vec<u8>,
    fds: Vec<Rc<OwnedFd>>,
}

impl Incoming {
    async fn run(mut self) {
        loop {
            if let Err(e) = self.handle_msg().await {
                log::error!("Could not handle incoming message: {}", ErrorFmt(e));
                self.con.kill();
                return;
            }
        }
    }

    async fn handle_msg(&mut self) -> Result<(), PwConError> {
        self.buf.clear();
        self.incoming.fill_msg_buf(16, &mut self.buf).await?;
        let id: u32 = uapi::pod_read(&self.buf[0..4]).unwrap();
        let p2: u32 = uapi::pod_read(&self.buf[4..8]).unwrap();
        let opcode = (p2 >> 24) as u8;
        let size = (p2 & 0xff_ffff) as usize;
        let _seq: u32 = uapi::pod_read(&self.buf[8..12]).unwrap();
        let n_fds: u32 = uapi::pod_read(&self.buf[12..16]).unwrap();
        for _ in 0..n_fds {
            match self.incoming.fds.pop_front() {
                Some(fd) => self.fds.push(fd),
                _ => return Err(PwConError::MissingFd),
            }
        }
        self.buf.clear();
        self.incoming.fill_msg_buf(size, &mut self.buf).await?;
        if let Err(e) = self.handle_msg_data(id, opcode) {
            log::warn!("Could not handle incoming message: {}", ErrorFmt(e));
        }
        self.fds.clear();
        Ok(())
    }

    fn handle_msg_data(&self, id: u32, opcode: u8) -> Result<(), PwConError> {
        let parser = PwParser::new(&self.buf, &self.fds);
        {
            let mut parser = parser;
            parser.skip()?;
            if parser.len() > 0 {
                let s1 = parser.read_struct()?;
                let mut p2 = s1.fields;
                while p2.len() > 0 {
                    let opcode = p2.read_id()?;
                    let s2 = p2.read_struct()?;
                    if opcode == FOOTER_REGISTRY_GENERATION {
                        let mut p3 = s2.fields;
                        let generation = p3.read_ulong()?;
                        self.con.registry_generation.set(generation);
                        log::debug!("registry generation = {}", generation);
                    } else {
                        log::warn!("Unknown message footer: {}", opcode);
                    }
                }
            }
        }
        if let Some(obj) = self.con.objects.get(&id) {
            'log: {
                if log::log_enabled!(log::Level::Trace) {
                    let s;
                    let op: &dyn Display = match obj.event_name(opcode) {
                        Some(e) => {
                            s = e;
                            if e == "Done" && obj.interface() == "core" {
                                break 'log;
                            }
                            &s
                        }
                        _ => &opcode,
                    };
                    log::trace!("EVENT {}@{}: `{}`:", obj.interface(), obj.data().id, op);
                    let mut parser = parser;
                    while parser.len() > 0 {
                        log::trace!("{:#?}", parser.read_pod().unwrap());
                    }
                }
            }
            obj.handle_msg(opcode, parser)?;
        }
        Ok(())
    }
}

const FOOTER_REGISTRY_GENERATION: u32 = 0;
