use std::{collections::HashMap, ptr::NonNull, rc::Rc, cell::Cell};

use anyhow::bail;
use core_shared::StringResourceName;

use crate::{
  config_node::ResourceConfig, helpers::read_cpp_str_vec, resource::Resource, sdk, SomeResult,
  VoidResult,
};

#[derive(Debug)]
pub struct AltResource {
  pub name: String,
  pub resource_type: String,
  pub main: String,
  pub client_type: String,
  pub client_main: String,
  pub path: String,
  pub client_files: Vec<String>,
  pub dependants: Vec<String>,
  pub dependencies: Vec<String>,
  pub config: ResourceConfig,

  valid: Cell<bool>,
}

impl AltResource {
  pub fn all() -> Vec<Rc<AltResource>> {
    Resource::with_alt_resources_ref(|v, _| v.resources.values().cloned().collect())
  }

  pub fn current() -> Rc<AltResource> {
    Resource::with_alt_resources_ref(|v, _| v.this_resource.as_ref().unwrap().clone())
  }

  pub fn get_by_name(name: &str) -> SomeResult<Rc<AltResource>> {
    Resource::with_alt_resources_ref(|v, _| v.get_by_name(name))
  }

  pub fn start(name: impl ToString) {
    let raw_ptr = unsafe { sdk::ICore::StartResource(name.to_string()) };
    Resource::with_alt_resources_mut(|mut v, _| v.add_resource_from_raw_ptr(raw_ptr));
  }

  pub fn restart(&self) -> VoidResult {
    self.assert_valid()?;
    unsafe { sdk::ICore::RestartResource(&self.name) }
    Ok(())
  }

  pub fn stop(&self) -> VoidResult {
    self.assert_valid()?;
    unsafe { sdk::ICore::StopResource(&self.name) }
    Ok(())
  }

  pub fn valid(&self) -> bool {
    self.valid.get()
  }

  fn assert_valid(&self) -> VoidResult {
    if !self.valid() {
      bail!(
        "Resource: '{}' is stopped and cannot be used anymore",
        self.name
      )
    }
    Ok(())
  }
}

type ResourcePtr = NonNull<sdk::alt::IResource>;

#[derive(Debug, Default)]
pub struct AltResourceManager {
  resources: HashMap<String, Rc<AltResource>>,
  this_resource: Option<Rc<AltResource>>,
}

impl AltResourceManager {
  pub fn init(&mut self, this_name: &StringResourceName) {
    let resources = unsafe { sdk::ICore::GetAllResources() };
    for v in resources.into_iter() {
      let raw_ptr = unsafe { sdk::read_resource_ptr_wrapper(v) };
      let resource = self.add_resource_from_raw_ptr(raw_ptr);
      if &resource.name == this_name {
        self.this_resource.replace(resource);
      }
    }

    if self.this_resource.is_none() {
      panic!("this resource is none");
    }
  }

  pub fn add_resource_from_raw_ptr(
    &mut self,
    raw_ptr: *mut sdk::alt::IResource,
  ) -> Rc<AltResource> {
    let ptr = NonNull::new(raw_ptr).unwrap();
    let name = get_resource_name(ptr);
    logger::debug!("adding resource: {name}");
    self.add_resource(name, ptr)
  }

  pub fn on_start(&mut self, resource_ptr: ResourcePtr) -> Rc<AltResource> {
    let name = get_resource_name(resource_ptr);
    logger::debug!("on start name: {name}");
    if let Some(resource) = self.resources.get(&name) {
      logger::debug!("resource already added");
      return resource.clone();
    }
    self.add_resource(name, resource_ptr)
  }

  pub fn on_stop(&mut self, resource_ptr: ResourcePtr) -> Rc<AltResource> {
    let name = get_resource_name(resource_ptr);
    logger::debug!("on stop name: {name}");

    let resource = self.resources.remove(&name).unwrap();
    resource.valid.set(false);
    resource
  }

  pub fn add_resource(&mut self, name: StringResourceName, ptr: ResourcePtr) -> Rc<AltResource> {
    use sdk::IResource::*;

    let raw_ptr = ptr.as_ptr();
    let instance = Rc::new(AltResource {
      name: name.clone(),
      resource_type: unsafe { GetType(raw_ptr) }.to_string(),
      path: unsafe { GetPath(raw_ptr) }.to_string(),
      main: unsafe { GetMain(raw_ptr) }.to_string(),
      client_type: unsafe { GetClientType(raw_ptr) }.to_string(),
      client_main: unsafe { GetClientMain(raw_ptr) }.to_string(),
      client_files: read_cpp_str_vec(unsafe { GetClientFiles(raw_ptr) }),
      dependants: read_cpp_str_vec(unsafe { GetDependants(raw_ptr) }),
      dependencies: read_cpp_str_vec(unsafe { GetDependencies(raw_ptr) }),
      config: ResourceConfig::new(unsafe { GetConfig(raw_ptr) }),

      valid: Cell::new(true),
    });

    self.resources.insert(name, instance.clone());
    instance
  }

  pub fn get_by_name(&self, name: &str) -> SomeResult<Rc<AltResource>> {
    self
      .resources
      .get(name)
      .cloned()
      .ok_or(anyhow::anyhow!("unknown resource name: {name}"))
  }
}

fn get_resource_name(resource_ptr: ResourcePtr) -> StringResourceName {
  unsafe { sdk::IResource::GetName(resource_ptr.as_ptr()) }.to_string()
}
