use core::{mem::MaybeUninit, time::Duration};

use abi::{
    error::Error,
    io::{
        AccessMode, DirectoryEntry, FileAttr, FileMetadataUpdate, FileMode, FileSync, OpenOptions,
        PollControl, RawFd, RemoveFlags, Rename, SeekFrom, TerminalOptions, TerminalSize,
        TimerOptions,
    },
    process::{ProcessWait, WaitFlags},
};
use libk::{
    block,
    task::thread::Thread,
    vfs::{File, Read, Seek, Write},
};
use libk_util::io::{ReadAt, WriteAt};

use crate::{
    fs,
    syscall::{at_fd, run_with_io, run_with_io_at},
};

// I/O
pub(crate) fn open(
    at: Option<RawFd>,
    path: &str,
    opts: OpenOptions,
    mode: FileMode,
) -> Result<RawFd, Error> {
    let thread = Thread::current();
    let process = thread.process();

    let res = run_with_io_at(&process, at, |at, mut io| {
        let file = io.ioctx_mut().open(Some(at), path, opts, mode)?;

        // TODO NO_CTTY?
        if process.session_terminal().is_none()
            && let Some(node) = file.node()
            && node.is_terminal()
        {
            log::debug!("Session terminal set for #{}: {}", process.id, path);
            process.set_session_terminal(node.clone());
        }

        let fd = io.files.place_file(file, true)?;
        Ok(fd)
    });
    log::debug!("Open {:?} -> {:?}", path, res);
    res
}

pub(crate) fn check_access(at: Option<RawFd>, path: &str, access: AccessMode) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io_at(&process, at, |at, mut io| {
        let node = if path.is_empty() {
            at
        } else {
            io.ioctx_mut().find(Some(at), path, true)?
        };

        io.ioctx().check_access(&node, access)?;

        Ok(())
    })
}

pub(crate) fn close(fd: RawFd) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let res = io.files.close_file(fd);

        if res == Err(Error::InvalidFile) {
            log::warn!("Double close of fd {:?} in process {}", fd, process.id);
        }

        res
    })
}

pub(crate) fn write(fd: RawFd, buffer: &[u8]) -> Result<usize, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| io.files.file(fd)?.write(buffer))
}

pub(crate) fn read(fd: RawFd, buffer: &mut [u8]) -> Result<usize, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| io.files.file(fd)?.read(buffer))
}

pub(crate) fn truncate(fd: RawFd, size: u64) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| io.files.file(fd)?.truncate(size))
}

pub(crate) fn fsync(fd: RawFd, what: FileSync) -> Result<(), Error> {
    let _ = fd;
    let _ = what;
    todo!()
}

pub(crate) fn read_at(fd: RawFd, pos: u64, buffer: &mut [u8]) -> Result<usize, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| io.files.file(fd)?.read_at(pos, buffer))
}

pub(crate) fn write_at(fd: RawFd, pos: u64, buffer: &[u8]) -> Result<usize, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| io.files.file(fd)?.write_at(pos, buffer))
}

pub(crate) fn seek(fd: RawFd, pos: SeekFrom, output: &mut u64) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| {
        *output = io.files.file(fd)?.seek(pos)?;
        Ok(())
    })
}

pub(crate) fn get_file_option(fd: RawFd, option: u32, buffer: &mut [u8]) -> Result<usize, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| io.files.file(fd)?.get_option(option, buffer))
}

pub(crate) fn set_file_option(fd: RawFd, option: u32, buffer: &[u8]) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| io.files.file(fd)?.set_option(option, buffer))
}

pub(crate) fn open_directory(at: Option<RawFd>, path: &str) -> Result<RawFd, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io_at(&process, at, |at, mut io| {
        let node = io.ioctx_mut().find(Some(at), path, true)?;
        let access = io.ioctx_mut().check_access(&node, AccessMode::READ)?;
        let file = node.open_directory(access)?;
        let fd = io.files.place_file(file, true)?;

        Ok(fd)
    })
}

pub(crate) fn read_directory_entries(
    fd: RawFd,
    entries: &mut [MaybeUninit<DirectoryEntry>],
) -> Result<usize, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| io.files.file(fd)?.read_dir(entries))
}

pub(crate) fn create_directory(at: Option<RawFd>, path: &str, mode: FileMode) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io_at(&process, at, |at, mut io| {
        io.ioctx_mut().create_directory(Some(at), path, mode)?;
        Ok(())
    })
}

pub(crate) fn create_symlink(at: Option<RawFd>, target: &str, path: &str) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io_at(&process, at, |at, mut io| {
        io.ioctx_mut()
            .create_symlink(Some(at), target, path, FileMode::default_dir())?;
        Ok(())
    })
}

pub(crate) fn remove(at: Option<RawFd>, path: &str, options: RemoveFlags) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io_at(&process, at, |at, mut io| {
        io.ioctx_mut().remove(Some(at), path, options)?;
        Ok(())
    })
}

pub(crate) fn rename(rename: &Rename) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();
    run_with_io(&process, |mut io| {
        let src_at = at_fd(&io, rename.source_at)?;
        let dst_at = at_fd(&io, rename.destination_at)?;

        io.ioctx_mut()
            .rename(src_at, rename.source, dst_at, rename.destination)
    })
}

pub(crate) fn clone_fd(source_fd: RawFd, target_fd: Option<RawFd>) -> Result<RawFd, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let file = io.files.file(source_fd)?.clone();

        let fd = match target_fd {
            Some(target_fd) => {
                io.files.set_file(target_fd, file)?;
                target_fd
            }
            None => io.files.place_file(file, true)?,
        };

        Ok(fd)
    })
}

pub(crate) fn update_metadata(
    at: Option<RawFd>,
    path: &str,
    update: &FileMetadataUpdate,
) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io_at(&process, at, |at, mut io| {
        let node = if path.is_empty() {
            at
        } else {
            io.ioctx_mut().find(Some(at), path, false)?
        };

        node.update_metadata(update)?;

        Ok(())
    })
}

pub(crate) fn get_metadata(
    at: Option<RawFd>,
    path: &str,
    buffer: &mut MaybeUninit<FileAttr>,
    follow: bool,
) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io_at(&process, at, |at, mut io| {
        let node = if path.is_empty() {
            at
        } else {
            io.ioctx_mut().find(Some(at), path, follow)?
        };

        let metadata = node.metadata()?;
        let size = node.size()?;

        let metadata = FileAttr {
            size,
            ty: node.ty(),
            mode: metadata.mode,
            uid: metadata.uid,
            gid: metadata.gid,
            inode: metadata.inode,
            block_count: metadata.block_count,
            block_size: metadata.block_size,
            ctime: metadata.ctime,
            mtime: metadata.mtime,
            atime: metadata.mtime,
        };
        buffer.write(metadata);

        Ok(())
    })
}

pub(crate) fn read_link(at: Option<RawFd>, path: &str, buffer: &mut [u8]) -> Result<usize, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io_at(&process, at, |at, mut io| {
        let node = if path.is_empty() {
            at
        } else {
            io.ioctx_mut().find(Some(at), path, false)?
        };

        node.read_link(buffer)
    })
}

pub(crate) fn device_request(
    fd: RawFd,
    option: u32,
    buffer: &mut [u8],
    len: usize,
) -> Result<usize, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| {
        io.files.file(fd)?.device_request(option, buffer, len)
    })
}

// Misc I/O
// pub(crate) fn open_channel(name: &str, subscribe: bool) -> Result<RawFd, Error> {
//     let thread = Thread::current();
//     let process = thread.process();
//
//     run_with_io(&process, |mut io| {
//         let file = File::new_message_channel(name, subscribe);
//         let fd = io.files.place_file(file, true)?;
//         Ok(fd)
//     })
// }

pub(crate) fn create_timer(options: TimerOptions) -> Result<RawFd, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let file = File::new_timer(options);
        let fd = io.files.place_file(file, true)?;
        Ok(fd)
    })
}

pub(crate) fn create_pid(wait: &ProcessWait, flags: WaitFlags) -> Result<RawFd, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let file = File::new_pid(process.clone(), wait, flags)?;
        let fd = io.files.place_file(file, true)?;
        Ok(fd)
    })
}

pub(crate) fn create_pty(
    options: &TerminalOptions,
    size: &TerminalSize,
    output: &mut [MaybeUninit<RawFd>; 2],
) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let (master, slave) = File::new_pseudo_terminal(*options, *size)?;
        let master_fd = io.files.place_file(master, true)?;
        let slave_fd = io.files.place_file(slave, true)?;

        log::debug!("create_pty() -> ({master_fd:?}, {slave_fd:?})");
        output[0].write(master_fd);
        output[1].write(slave_fd);

        Ok(())
    })
}

pub(crate) fn create_shared_memory(size: usize) -> Result<RawFd, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let file = File::new_shared_memory(size)?;
        let fd = io.files.place_file(file, true)?;
        Ok(fd)
    })
}

pub(crate) fn create_poll_channel() -> Result<RawFd, Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let poll = File::new_poll_channel();
        let fd = io.files.place_file(poll, true)?;

        Ok(fd)
    })
}

pub(crate) fn create_pipe(ends: &mut [MaybeUninit<RawFd>; 2]) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |mut io| {
        let (read, write) = File::new_pipe_pair(256);

        let read_fd = io.files.place_file(read, true)?;
        let write_fd = io.files.place_file(write, true)?;

        ends[0].write(read_fd);
        ends[1].write(write_fd);

        Ok(())
    })
}

pub(crate) fn poll_channel_wait(
    poll_fd: RawFd,
    timeout: &Option<Duration>,
    retain: bool,
    output: &mut Option<(RawFd, Result<(), Error>)>,
) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| {
        let poll_file = io.files.file(poll_fd)?;
        let poll = poll_file.as_poll_channel()?;

        *output = block! {
            poll.wait(*timeout, retain).await
        }?;

        Ok(())
    })
}

pub(crate) fn poll_channel_control(
    poll_fd: RawFd,
    control: PollControl,
    fd: RawFd,
) -> Result<(), Error> {
    let thread = Thread::current();
    let process = thread.process();

    run_with_io(&process, |io| {
        let poll_file = io.files.file(poll_fd)?;
        let poll = poll_file.as_poll_channel()?;

        match control {
            PollControl::AddFd => {
                let polled_file = io.files.file(fd)?.clone();
                poll.add(fd, polled_file);
            }
            PollControl::RemoveFd => {
                poll.remove(fd);
            }
        }

        Ok(())
    })
}

// pub(crate) fn send_message(
//     fd: RawFd,
//     message: &SentMessage<'_>,
//     destination: MessageDestination,
// ) -> Result<(), Error> {
//     let thread = Thread::current();
//     let process = thread.process();
//
//     run_with_io(&process, |io| {
//         let file = io.files.file(fd)?;
//         let channel = file.as_message_channel()?;
//
//         match message {
//             &SentMessage::File(fd) => {
//                 let sent_file = io.files.file(fd)?;
//
//                 channel.send_message(MessagePayload::File(sent_file.clone()), destination)?;
//             }
//             &SentMessage::Data(data) => {
//                 channel.send_message(MessagePayload::Data(Box::from(data)), destination)?;
//             }
//         }
//
//         Ok(())
//     })
// }

// pub(crate) fn receive_message(
//     fd: RawFd,
//     metadata: &mut MaybeUninit<ReceivedMessageMetadata>,
//     buf: &mut [u8],
//     from: &mut MaybeUninit<ChannelPublisherId>,
// ) -> Result<(), Error> {
//     let thread = Thread::current();
//     let process = thread.process();
//
//     run_with_io(&process, |mut io| {
//         let file = io.files.file(fd)?;
//         let channel = file.as_message_channel()?;
//
//         let message = channel.receive_message()?;
//
//         from.write(message.source);
//
//         match &message.payload {
//             MessagePayload::Data(data) => {
//                 // TODO allow truncated messages?
//                 let len = data.len();
//                 if buf.len() < len {
//                     return Err(Error::MissingData);
//                 }
//
//                 metadata.write(ReceivedMessageMetadata::Data(len));
//                 buf[..len].copy_from_slice(data);
//
//                 Ok(())
//             }
//             MessagePayload::File(file) => {
//                 let fd = io.files.place_file(file.clone(), true)?;
//
//                 metadata.write(ReceivedMessageMetadata::File(fd));
//
//                 Ok(())
//             }
//         }
//     })
// }

pub(crate) fn filesystem_control(
    fd: Option<RawFd>,
    option: u32,
    buffer: &mut [u8],
    len: usize,
) -> Result<usize, Error> {
    match fd {
        None => fs::global_control(option, buffer, len),
        Some(fd) => {
            let thread = Thread::current();
            let process = thread.process();

            run_with_io(&process, |io| {
                let file = io.files.file(fd)?;
                let node = file.node().ok_or(Error::InvalidOperation)?;
                if let Some(fs) = node.filesystem() {
                    fs::filesystem_control(fs, option, buffer, len)
                } else {
                    Err(Error::InvalidArgument)
                }
            })
        }
    }
}
