use crate::errors::Result;
use crate::history::HistoryItem;
use crate::{cache::LocalNovelCache, errors::Errors};

use anyhow::anyhow;
use async_trait::async_trait;
use std::ops::{Deref, DerefMut};
use std::{
    io::SeekFrom,
    path::{Path, PathBuf},
    sync::Arc,
};
use tokio::fs::File;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, BufReader};
use tokio::sync::Mutex;

use super::{Novel, NovelChapters};

#[derive(Debug, Clone)]
pub struct LocalNovel {
    pub file: Arc<Mutex<File>>,
    pub novel_chapters: NovelChapters<(String, usize)>,
    pub encoding: &'static encoding_rs::Encoding,
    pub path: PathBuf,
}

impl Deref for LocalNovel {
    type Target = NovelChapters<(String, usize)>;
    fn deref(&self) -> &Self::Target {
        &self.novel_chapters
    }
}

impl DerefMut for LocalNovel {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.novel_chapters
    }
}

impl LocalNovel {
    async fn from_cache(value: LocalNovelCache) -> Result<Self> {
        let file = File::open(&value.path).await?;
        Ok(Self {
            file: Arc::new(Mutex::new(file)),
            novel_chapters: NovelChapters {
                chapters: Some(value.chapters),
                current_chapter: value.current_chapter,
                line_percent: value.line_percent,
            },
            encoding: value.encoding,
            path: value.path,
        })
    }

    pub async fn from_path<T: AsRef<Path>>(path: T) -> Result<Self> {
        let path = path.as_ref().to_path_buf().canonicalize()?;
        match LocalNovelCache::try_from(path.as_path()) {
            Ok(cache) => Self::from_cache(cache).await,
            Err(_) => Self::new(path).await,
        }
    }

    pub async fn new<T>(path: T) -> Result<Self>
    where
        T: AsRef<Path>,
    {
        let path = path.as_ref().to_path_buf().canonicalize()?;

        let file = File::open(&path).await?;
        let encoding = Self::get_file_encoding(file).await?;

        let file = File::open(&path).await?;

        Ok(Self {
            file: Arc::new(Mutex::new(file)),
            novel_chapters: NovelChapters::new(),
            encoding,
            path,
        })
    }

    async fn get_file_encoding(mut file: File) -> std::io::Result<&'static encoding_rs::Encoding> {
        let mut buffer = vec![];

        file.read_to_end(&mut buffer).await?;
        if let (_, encoding, false) = encoding_rs::UTF_8.decode(&buffer) {
            return Ok(encoding);
        }

        if let (_, encoding, false) = encoding_rs::GBK.decode(&buffer) {
            return Ok(encoding);
        }

        Err(std::io::Error::new(
            std::io::ErrorKind::Unsupported,
            "Unsupported encoding",
        ))
    }
}

#[async_trait]
impl Novel for LocalNovel {
    type Chapter = (String, usize);
    type Args = PathBuf;

    async fn init(args: Self::Args) -> Result<Self> {
        Self::from_path(args).await
    }

    fn request_chapters<T: FnMut(Result<Vec<Self::Chapter>>) + Send + 'static>(
        &self,
        mut callback: T,
    ) -> Result<()> {
        let path = self.path.clone();
        let encoding = self.encoding;

        tokio::task::spawn(async move {
            let res = async {
                let file = File::open(path).await?;

                let mut buf_reader = BufReader::new(file);
                let regexp = regex::Regex::new(r"第.+章").unwrap();
                let mut chapter_offset = Vec::new();
                let mut offset = 0;

                let mut line = vec![];

                while let Ok(chunk_size) = buf_reader.read_until(b'\n', &mut line).await {
                    if chunk_size == 0 {
                        break;
                    }
                    let (new_line, _, _) = encoding.decode(&line);

                    if regexp.is_match(&new_line) {
                        chapter_offset.push((new_line.trim().to_string(), offset));
                    }
                    line.clear();
                    offset += chunk_size;
                }
                Ok(chapter_offset)
            }
            .await;

            callback(res);
        });
        Ok(())
    }

    fn get_content<T: FnMut(Result<String>) + Send + 'static>(
        &mut self,
        mut callback: T,
    ) -> Result<()> {
        let start = if self.current_chapter == 0 {
            0
        } else {
            let (_, start) = self.get_chapters_result()?[self.current_chapter];
            start.to_owned()
        };
        let is_last = self.get_chapters_result()?.is_empty()
            || self.current_chapter + 1 >= self.get_chapters_result()?.len();

        let end = self.chapters.as_ref().and_then(|chapters| {
            chapters
                .get(self.current_chapter + 1)
                .map(|chapter| chapter.1)
        });

        let file = self.file.clone();
        let encoding = self.encoding;
        tokio::spawn(async move {
            let res = async {
                let mut file = file.lock().await;

                let end = if is_last {
                    file.metadata().await?.len() as usize
                } else {
                    end.ok_or(anyhow!("找不到下一章"))?
                };

                let mut buffer = vec![0; end - start];
                file.seek(SeekFrom::Start(start as u64)).await?;
                file.read_exact(&mut buffer).await?;

                let (str, _, has_error) = encoding.decode(&buffer);
                if has_error {
                    return Err(anyhow::anyhow!("解码错误").into());
                }
                Ok::<String, Errors>(str.to_string())
            }
            .await;

            callback(res);
        });
        Ok(())
    }

    fn get_current_chapter_name(&self) -> Result<String> {
        self.get_current_chapter().map(|chapter| chapter.0)
    }

    fn get_chapters_names(&self) -> Result<Vec<(String, usize)>> {
        Ok(self
            .get_chapters_result()?
            .iter()
            .enumerate()
            .map(|(index, item)| (item.0.clone(), index))
            .collect())
    }

    fn to_history_item(&self) -> Result<HistoryItem> {
        let local_novel_cache = LocalNovelCache::try_from(self)?;
        local_novel_cache.save()?;
        Ok(local_novel_cache.into())
    }

    fn get_id(&self) -> String {
        self.path.to_string_lossy().to_string()
    }
}
