//! Offline marketplace folder classification and capability-backed listing state.

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]

use crate::agent_manager::EventRegistry;
use crate::client_core::ClientWeakHandle;
use crate::marketplace::{
    MarketplaceFolderRole, MarketplaceListingStatus, MarketplaceValidationFlags,
};
use crate::{Error, GridClient, Inventory, InventoryFolder};
use libremetaverse_structured_data::{OSD, OSDParser};
use libremetaverse_types::compat::{
    CancellationToken, EventHandler, ExternalError, HttpResponse, Subscription, Uri,
};
use libremetaverse_types::{FolderType, UUID};
use serde_json::{Value, json};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};

const JSON_CONTENT_TYPE: &str = "application/json";

fn read<T>(value: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
    value
        .read()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

fn write<T>(value: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
    value
        .write()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

fn folder_at(inventory: &Inventory, id: UUID) -> Option<InventoryFolder> {
    inventory
        .get_node_or_default(id)
        .ok()
        .flatten()
        .and_then(|node| node.data())
        .and_then(|value| value.as_any().downcast_ref::<InventoryFolder>().cloned())
}

fn child_folders(inventory: &Inventory, parent: UUID) -> Vec<InventoryFolder> {
    inventory
        .get_contents_with_uuid(parent)
        .unwrap_or_default()
        .into_iter()
        .filter_map(|value| value.as_any().downcast_ref::<InventoryFolder>().cloned())
        .collect()
}

/// Pure inventory-tree decisions used by marketplace workflows.
pub struct MarketplaceFolderClassifier;

impl MarketplaceFolderClassifier {
    pub fn get_listings_root(inventory: &Inventory) -> Result<UUID, Error> {
        Ok(inventory
            .find_folder_for_type(FolderType::MarketplaceListings)
            .unwrap_or_else(UUID::zero))
    }

    pub fn get_role(
        folder_id: UUID,
        inventory: &Inventory,
    ) -> Result<MarketplaceFolderRole, Error> {
        let Some(folder) = folder_at(inventory, folder_id) else {
            return Ok(MarketplaceFolderRole::None);
        };
        if folder.preferred_type() == FolderType::MarketplaceListings {
            return Ok(MarketplaceFolderRole::ListingsRoot);
        }
        let Some(parent) = folder_at(inventory, folder.base.parent_uuid()) else {
            return Ok(MarketplaceFolderRole::None);
        };
        if parent.preferred_type() == FolderType::MarketplaceListings {
            return Ok(MarketplaceFolderRole::Listing);
        }
        let parent_role = Self::get_role(parent.base.uuid(), inventory)?;
        if folder.preferred_type() == FolderType::MarketplaceVersion
            && parent_role == MarketplaceFolderRole::Listing
        {
            return Ok(MarketplaceFolderRole::Version);
        }
        if folder.preferred_type() == FolderType::MarketplaceStock
            && parent_role == MarketplaceFolderRole::Version
        {
            return Ok(MarketplaceFolderRole::Stock);
        }
        if parent_role == MarketplaceFolderRole::Version {
            return Ok(MarketplaceFolderRole::Content);
        }
        Ok(MarketplaceFolderRole::None)
    }

    pub fn is_marketplace_folder(folder_id: UUID, inventory: &Inventory) -> Result<bool, Error> {
        Ok(Self::get_role(folder_id, inventory)? != MarketplaceFolderRole::None)
    }

    pub fn get_listing_folder(folder_id: UUID, inventory: &Inventory) -> Result<UUID, Error> {
        let mut current = folder_id;
        for _ in 0..512 {
            match Self::get_role(current, inventory)? {
                MarketplaceFolderRole::Listing => return Ok(current),
                MarketplaceFolderRole::ListingsRoot | MarketplaceFolderRole::None => {
                    return Ok(UUID::zero());
                }
                _ => {
                    let Some(folder) = folder_at(inventory, current) else {
                        return Ok(UUID::zero());
                    };
                    current = folder.base.parent_uuid();
                }
            }
        }
        Ok(UUID::zero())
    }

    pub fn get_version_folder(listing: UUID, inventory: &Inventory) -> Result<UUID, Error> {
        if Self::get_role(listing, inventory)? != MarketplaceFolderRole::Listing {
            return Ok(UUID::zero());
        }
        Ok(child_folders(inventory, listing)
            .into_iter()
            .find(|folder| folder.preferred_type() == FolderType::MarketplaceVersion)
            .map_or_else(UUID::zero, |folder| folder.base.uuid()))
    }

    pub fn get_stock_count(version: UUID, inventory: &Inventory) -> Result<i32, Error> {
        if version == UUID::zero() {
            return Ok(0);
        }
        let count = inventory
            .get_contents_with_uuid(version)
            .unwrap_or_default()
            .iter()
            .filter(|value| value.as_any().downcast_ref::<InventoryFolder>().is_none())
            .count();
        Ok(i32::try_from(count).unwrap_or(i32::MAX))
    }

    pub fn get_all_listing_folder_ids(inventory: &Inventory) -> Result<Vec<UUID>, Error> {
        let root = Self::get_listings_root(inventory)?;
        if root == UUID::zero() {
            return Ok(Vec::new());
        }
        Ok(child_folders(inventory, root)
            .into_iter()
            .map(|folder| folder.base.uuid())
            .collect())
    }

    pub fn validate_listing(
        listing: UUID,
        inventory: &Inventory,
    ) -> Result<MarketplaceValidationFlags, Error> {
        if Self::get_role(listing, inventory)? != MarketplaceFolderRole::Listing {
            return Ok(MarketplaceValidationFlags::INVALID_STRUCTURE);
        }
        let versions: Vec<_> = child_folders(inventory, listing)
            .into_iter()
            .filter(|folder| folder.preferred_type() == FolderType::MarketplaceVersion)
            .collect();
        match versions.as_slice() {
            [] => Ok(MarketplaceValidationFlags::MISSING_VERSION_FOLDER),
            [version] if Self::get_stock_count(version.base.uuid(), inventory)? == 0 => {
                Ok(MarketplaceValidationFlags::EMPTY_LISTING)
            }
            [_] => Ok(MarketplaceValidationFlags::VALID),
            _ => Ok(MarketplaceValidationFlags::MULTIPLE_VERSION_FOLDERS),
        }
    }
}

#[derive(Clone, Debug)]
pub struct MarketplaceListing {
    edit_url: Option<String>,
    last_sync_utc: SystemTime,
    listing_folder_uuid: UUID,
    listing_id: i32,
    status: MarketplaceListingStatus,
    stock_count: i32,
    version_folder_uuid: UUID,
}

impl MarketplaceListing {
    pub fn new() -> Result<Self, Error> {
        Ok(Self {
            edit_url: None,
            last_sync_utc: UNIX_EPOCH,
            listing_folder_uuid: UUID::zero(),
            listing_id: 0,
            status: MarketplaceListingStatus::Unknown,
            stock_count: 0,
            version_folder_uuid: UUID::zero(),
        })
    }
    pub fn edit_url(&self) -> Option<String> {
        self.edit_url.clone()
    }
    pub fn set_edit_url(&mut self, value: Option<String>) {
        self.edit_url = value;
    }
    pub fn last_sync_utc(&self) -> SystemTime {
        self.last_sync_utc
    }
    pub fn set_last_sync_utc(&mut self, value: SystemTime) {
        self.last_sync_utc = value;
    }
    pub fn listing_folder_uuid(&self) -> UUID {
        self.listing_folder_uuid
    }
    pub fn set_listing_folder_uuid(&mut self, value: UUID) {
        self.listing_folder_uuid = value;
    }
    pub fn listing_id(&self) -> i32 {
        self.listing_id
    }
    pub fn set_listing_id(&mut self, value: i32) {
        self.listing_id = value;
    }
    pub fn status(&self) -> MarketplaceListingStatus {
        self.status
    }
    pub fn set_status(&mut self, value: MarketplaceListingStatus) {
        self.status = value;
    }
    pub fn stock_count(&self) -> i32 {
        self.stock_count
    }
    pub fn set_stock_count(&mut self, value: i32) {
        self.stock_count = value;
    }
    pub fn version_folder_uuid(&self) -> UUID {
        self.version_folder_uuid
    }
    pub fn set_version_folder_uuid(&mut self, value: UUID) {
        self.version_folder_uuid = value;
    }
}

#[derive(Clone)]
pub struct MarketplaceErrorEventArgs {
    message: String,
    exception: Option<Arc<ExternalError>>,
}
impl MarketplaceErrorEventArgs {
    pub fn new(message: String, exception: Option<Arc<ExternalError>>) -> Result<Self, Error> {
        Ok(Self { message, exception })
    }
    pub fn message(&self) -> String {
        self.message.clone()
    }
    pub fn exception(&self) -> Option<Arc<ExternalError>> {
        self.exception.clone()
    }
}

#[derive(Clone)]
pub struct MarketplaceListingChangedEventArgs {
    listing_id: i32,
    listing: Option<Arc<MarketplaceListing>>,
}
impl MarketplaceListingChangedEventArgs {
    pub fn new(listing_id: i32, listing: Option<Arc<MarketplaceListing>>) -> Result<Self, Error> {
        Ok(Self {
            listing_id,
            listing,
        })
    }
    pub fn listing_id(&self) -> i32 {
        self.listing_id
    }
    pub fn listing(&self) -> Option<Arc<MarketplaceListing>> {
        self.listing.clone()
    }
}

#[derive(Clone)]
pub struct MarketplaceListingsSyncedEventArgs {
    listings: Vec<MarketplaceListing>,
}
impl MarketplaceListingsSyncedEventArgs {
    pub fn new(listings: Vec<MarketplaceListing>) -> Result<Self, Error> {
        Ok(Self { listings })
    }
    pub fn listings(&self) -> Vec<MarketplaceListing> {
        self.listings.clone()
    }
}

enum MarketplaceClient {
    Standalone(GridClient),
    ClientOwned(ClientWeakHandle),
}

struct MarketplaceState {
    client: RwLock<MarketplaceClient>,
    by_id: RwLock<HashMap<i32, MarketplaceListing>>,
    by_folder: RwLock<HashMap<UUID, MarketplaceListing>>,
    errors: EventRegistry<MarketplaceErrorEventArgs>,
    changed: EventRegistry<MarketplaceListingChangedEventArgs>,
    synced: EventRegistry<MarketplaceListingsSyncedEventArgs>,
    live_mutations: AtomicBool,
}

#[derive(Clone)]
pub struct MarketplaceManager(Arc<MarketplaceState>);

impl MarketplaceManager {
    pub fn new(client: GridClient) -> Result<Self, Error> {
        Ok(Self::with_client(MarketplaceClient::Standalone(client)))
    }

    fn with_client(client: MarketplaceClient) -> Self {
        Self(Arc::new(MarketplaceState {
            client: RwLock::new(client),
            by_id: RwLock::new(HashMap::new()),
            by_folder: RwLock::new(HashMap::new()),
            errors: EventRegistry::default(),
            changed: EventRegistry::default(),
            synced: EventRegistry::default(),
            live_mutations: AtomicBool::new(false),
        }))
    }

    pub(crate) fn native_new_client_owned(client: &GridClient) -> Self {
        Self::with_client(MarketplaceClient::ClientOwned(client.native_weak_handle()))
    }

    pub(crate) fn native_bind_to_client(&self, client: &GridClient) {
        *write(&self.0.client) = MarketplaceClient::ClientOwned(client.native_weak_handle());
    }

    fn client(&self) -> Result<GridClient, Error> {
        match &*read(&self.0.client) {
            MarketplaceClient::Standalone(client) => Ok(client.clone()),
            MarketplaceClient::ClientOwned(client) => {
                client.upgrade().ok_or(Error::InvalidOperation)
            }
        }
    }

    /// Explicitly enables mutation requests to non-test capability hosts.
    pub fn enable_live_mutations(&self) {
        self.0.live_mutations.store(true, Ordering::Release);
    }

    pub fn subscribe_error(
        &self,
        handler: EventHandler<MarketplaceErrorEventArgs>,
    ) -> Subscription {
        self.0.errors.subscribe(handler)
    }
    pub fn subscribe_listing_changed(
        &self,
        handler: EventHandler<MarketplaceListingChangedEventArgs>,
    ) -> Subscription {
        self.0.changed.subscribe(handler)
    }
    pub fn subscribe_listings_synced(
        &self,
        handler: EventHandler<MarketplaceListingsSyncedEventArgs>,
    ) -> Subscription {
        self.0.synced.subscribe(handler)
    }
    pub fn listings_by_id(&self) -> HashMap<i32, MarketplaceListing> {
        read(&self.0.by_id).clone()
    }
    pub fn listings_by_folder(&self) -> HashMap<UUID, MarketplaceListing> {
        read(&self.0.by_folder).clone()
    }
    pub fn try_get_by_id(&self, id: i32, output: &mut Option<MarketplaceListing>) -> bool {
        *output = read(&self.0.by_id).get(&id).cloned();
        output.is_some()
    }
    pub fn try_get_by_folder(&self, folder: UUID, output: &mut Option<MarketplaceListing>) -> bool {
        *output = read(&self.0.by_folder).get(&folder).cloned();
        output.is_some()
    }

    fn emit_error(&self, message: impl Into<String>, error: Option<&Error>) {
        let exception = error.map(|error| Arc::new(ExternalError(error.to_string())));
        self.0.errors.emit(MarketplaceErrorEventArgs {
            message: message.into(),
            exception,
        });
    }

    async fn base_uri(&self, token: &CancellationToken) -> Result<Uri, Error> {
        token.throw_if_cancellation_requested()?;
        let simulator = self
            .client()?
            .native_network()?
            .native_current_sim()
            .ok_or(Error::InvalidOperation)?;
        if let Some(caps) = simulator.native_caps()
            && let Some(uri) = caps.capability_uri("DirectDelivery".into())?
        {
            return Ok(uri);
        }
        // Seed discovery runs independently of callers. If it has not installed
        // its map yet, resolve the one required capability through the same
        // bounded, injected HTTP client instead of racing the background task.
        let seed = simulator.seed_capability().ok_or(Error::InvalidOperation)?;
        let simulator_client = simulator.native_data_arc().client.clone();
        let (response, body) = simulator_client
            .native_http_caps_client()
            .get(seed, token.clone(), None)
            .await?;
        if !(200..300).contains(&response.status_code) {
            return Err(Error::InvalidOperation);
        }
        let OSD::Map(map) = OSDParser::deserialize_with_bytes(body)? else {
            return Err(Error::InvalidOperation);
        };
        map.get("DirectDelivery")
            .and_then(|value| value.as_uri().ok().flatten())
            .ok_or(Error::InvalidOperation)
    }

    async fn request(
        &self,
        method: &str,
        path: &str,
        payload: Vec<u8>,
        token: CancellationToken,
        mutation: bool,
    ) -> Result<(HttpResponse, Vec<u8>), Error> {
        let base = self.base_uri(&token).await?;
        if mutation
            && !crate::is_offline_fixture_uri(&base)
            && !self.0.live_mutations.load(Ordering::Acquire)
        {
            return Err(Error::InvalidOperation);
        }
        let uri = Uri(format!("{}/{}", base.0.trim_end_matches('/'), path));
        let client = self.client()?.native_http_caps_client();
        match method {
            "GET" => client.get(uri, token, None).await,
            "POST" => {
                client
                    .post_with_uri_string_bytes_cancellation_token_i_progress(
                        uri,
                        JSON_CONTENT_TYPE.into(),
                        payload,
                        token,
                        None,
                    )
                    .await
            }
            "PUT" => {
                client
                    .put_with_uri_string_bytes_cancellation_token_i_progress(
                        uri,
                        JSON_CONTENT_TYPE.into(),
                        payload,
                        token,
                        None,
                    )
                    .await
            }
            "DELETE" => {
                client
                    .delete_with_uri_string_bytes_cancellation_token_i_progress(
                        uri,
                        JSON_CONTENT_TYPE.into(),
                        payload,
                        token,
                        None,
                    )
                    .await
            }
            _ => Err(Error::Argument),
        }
    }

    fn parse_listings(bytes: &[u8]) -> Result<Vec<MarketplaceListing>, Error> {
        let root: Value = serde_json::from_slice(bytes).map_err(|error| Error::Parse {
            position: error.column(),
            context: "marketplace JSON",
        })?;
        let array = root
            .get("listings")
            .and_then(Value::as_array)
            .ok_or(Error::Parse {
                position: 0,
                context: "marketplace listings array",
            })?;
        array.iter().map(Self::parse_listing).collect()
    }

    fn parse_listing(value: &Value) -> Result<MarketplaceListing, Error> {
        let mut listing = MarketplaceListing::new()?;
        listing.listing_id = value
            .get("id")
            .and_then(Value::as_i64)
            .and_then(|id| i32::try_from(id).ok())
            .unwrap_or_default();
        listing.status = match value.get("is_listed").and_then(Value::as_bool) {
            Some(true) => MarketplaceListingStatus::Listed,
            Some(false) => MarketplaceListingStatus::Unlisted,
            None => MarketplaceListingStatus::Unknown,
        };
        listing.edit_url = value
            .get("edit_url")
            .and_then(Value::as_str)
            .map(str::to_owned);
        if let Some(info) = value.get("inventory_info") {
            listing.listing_folder_uuid = parse_uuid(info.get("listing_folder_id"));
            listing.version_folder_uuid = parse_uuid(info.get("version_folder_id"));
            listing.stock_count = info
                .get("count_on_hand")
                .and_then(Value::as_i64)
                .and_then(|count| i32::try_from(count).ok())
                .unwrap_or_default();
        }
        listing.last_sync_utc = SystemTime::now();
        Ok(listing)
    }

    fn replace_cache(&self, listings: &[MarketplaceListing]) {
        let by_id = listings
            .iter()
            .cloned()
            .map(|listing| (listing.listing_id, listing))
            .collect();
        let by_folder = listings
            .iter()
            .filter(|listing| listing.listing_folder_uuid != UUID::zero())
            .cloned()
            .map(|listing| (listing.listing_folder_uuid, listing))
            .collect();
        *write(&self.0.by_id) = by_id;
        *write(&self.0.by_folder) = by_folder;
    }

    fn cache_listing(&self, listing: MarketplaceListing) {
        write(&self.0.by_id).insert(listing.listing_id, listing.clone());
        if listing.listing_folder_uuid != UUID::zero() {
            write(&self.0.by_folder).insert(listing.listing_folder_uuid, listing);
        }
    }

    pub async fn fetch_listings(&self, ct: Option<CancellationToken>) -> Result<(), Error> {
        let token = ct.unwrap_or_default();
        let response = self
            .request("GET", "listings", Vec::new(), token, false)
            .await;
        let (response, body) = match response {
            Ok(value) => value,
            Err(Error::Cancelled) => return Err(Error::Cancelled),
            Err(error) => {
                self.emit_error("Failed to fetch marketplace listings", Some(&error));
                return Ok(());
            }
        };
        if !(200..300).contains(&response.status_code) {
            self.emit_error(
                format!(
                    "Marketplace listings request returned HTTP {}",
                    response.status_code
                ),
                None,
            );
            return Ok(());
        }
        match Self::parse_listings(&body) {
            Ok(listings) => {
                self.replace_cache(&listings);
                self.0
                    .synced
                    .emit(MarketplaceListingsSyncedEventArgs { listings });
            }
            Err(error) => self.emit_error("Invalid marketplace listings response", Some(&error)),
        }
        Ok(())
    }

    pub async fn create_listing(
        &self,
        folder: UUID,
        version: UUID,
        name: String,
        count: Option<i32>,
        ct: Option<CancellationToken>,
    ) -> Result<Option<MarketplaceListing>, Error> {
        let body = json!({"inventory_info": {"listing_folder_id": folder.to_string(), "version_folder_id": version.to_string(), "count_on_hand": count, "name": name}});
        let token = ct.unwrap_or_default();
        let result = self
            .request(
                "POST",
                "listings",
                serde_json::to_vec(&body).map_err(|_| Error::InvalidOperation)?,
                token,
                true,
            )
            .await;
        let Some(listing) = self.handle_listing_response(result, "create marketplace listing")?
        else {
            return Ok(None);
        };
        self.cache_listing(listing.clone());
        self.0.changed.emit(MarketplaceListingChangedEventArgs {
            listing_id: listing.listing_id,
            listing: Some(Arc::new(listing.clone())),
        });
        Ok(Some(listing))
    }

    pub async fn delete_listing(
        &self,
        id: i32,
        ct: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        let token = ct.unwrap_or_default();
        let result = self
            .request("DELETE", &format!("listing/{id}"), Vec::new(), token, true)
            .await;
        let (response, _) = match result {
            Ok(value) => value,
            Err(Error::Cancelled) => return Err(Error::Cancelled),
            Err(error) => {
                self.emit_error("Failed to delete marketplace listing", Some(&error));
                return Ok(false);
            }
        };
        if !(200..300).contains(&response.status_code) {
            self.emit_error(
                format!("Marketplace delete returned HTTP {}", response.status_code),
                None,
            );
            return Ok(false);
        }
        if let Some(old) = write(&self.0.by_id).remove(&id) {
            write(&self.0.by_folder).remove(&old.listing_folder_uuid);
        }
        self.0.changed.emit(MarketplaceListingChangedEventArgs {
            listing_id: id,
            listing: None,
        });
        Ok(true)
    }

    pub async fn activate_listing(
        &self,
        id: i32,
        ct: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        self.set_listing_active(id, true, ct).await
    }
    pub async fn deactivate_listing(
        &self,
        id: i32,
        ct: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        self.set_listing_active(id, false, ct).await
    }

    async fn set_listing_active(
        &self,
        id: i32,
        active: bool,
        ct: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        let Some(cached) = read(&self.0.by_id).get(&id).cloned() else {
            return Ok(false);
        };
        let body = json!({"id": id, "is_listed": active, "inventory_info": {"listing_folder_id": cached.listing_folder_uuid.to_string(), "version_folder_id": cached.version_folder_uuid.to_string(), "count_on_hand": cached.stock_count}});
        let result = self
            .request(
                "PUT",
                &format!("listing/{id}"),
                serde_json::to_vec(&body).map_err(|_| Error::InvalidOperation)?,
                ct.unwrap_or_default(),
                true,
            )
            .await;
        let Some(listing) = self.handle_listing_response(result, "update marketplace listing")?
        else {
            return Ok(false);
        };
        self.cache_listing(listing.clone());
        self.0.changed.emit(MarketplaceListingChangedEventArgs {
            listing_id: id,
            listing: Some(Arc::new(listing)),
        });
        Ok(true)
    }

    fn handle_listing_response(
        &self,
        result: Result<(HttpResponse, Vec<u8>), Error>,
        operation: &str,
    ) -> Result<Option<MarketplaceListing>, Error> {
        let (response, body) = match result {
            Ok(value) => value,
            Err(Error::Cancelled) => return Err(Error::Cancelled),
            Err(error) => {
                self.emit_error(format!("Failed to {operation}"), Some(&error));
                return Ok(None);
            }
        };
        if !(200..300).contains(&response.status_code) {
            self.emit_error(
                format!(
                    "Marketplace operation returned HTTP {}",
                    response.status_code
                ),
                None,
            );
            return Ok(None);
        }
        match Self::parse_listings(&body)
            .and_then(|mut listings| listings.drain(..).next().ok_or(Error::InvalidOperation))
        {
            Ok(listing) => Ok(Some(listing)),
            Err(error) => {
                self.emit_error("Invalid marketplace listing response", Some(&error));
                Ok(None)
            }
        }
    }
}

fn parse_uuid(value: Option<&Value>) -> UUID {
    value
        .and_then(Value::as_str)
        .and_then(|value| UUID::new_with_string(value.to_owned()).ok())
        .unwrap_or_else(UUID::zero)
}
