use super::{
    get_player_item_or_fail, parsing::parse_count, search_items_for_user, user_error,
    ItemSearchParams, UResult, UserVerb, UserVerbRef, VerbContext,
};
#[double]
use crate::db::DBTrans;
use crate::{
    models::{
        item::{Item, ItemFlag, LocationActionType},
        task::{Task, TaskDetails, TaskMeta},
    },
    regular_tasks::{
        queued_command::{queue_command, QueueCommand, QueueCommandHandler, QueuedCommandContext},
        TaskHandler, TaskRunContext,
    },
    services::{
        capacity::{check_item_ref_capacity, CapacityLevel},
        comms::broadcast_to_room,
    },
    static_content::possession_type::possession_data,
    DResult,
};
use ansi::ansi;
use async_trait::async_trait;
use chrono::Utc;
use mockall_double::double;
use std::time;

pub struct ExpireItemTaskHandler;
#[async_trait]
impl TaskHandler for ExpireItemTaskHandler {
    async fn do_task(&self, ctx: &mut TaskRunContext) -> DResult<Option<time::Duration>> {
        let item_code = match &mut ctx.task.details {
            TaskDetails::ExpireItem { item_code } => item_code,
            _ => Err("Expected ExpireItem type")?,
        };
        let item = match ctx
            .trans
            .find_item_by_type_code("possession", item_code)
            .await?
        {
            None => {
                return Ok(None);
            }
            Some(it) => it,
        };
        let (loc_type, loc_code) = match item.location.split_once("/") {
            None => return Ok(None),
            Some(p) => p,
        };

        if loc_type != "room" {
            return Ok(None);
        }

        let loc_item = match ctx.trans.find_item_by_type_code(loc_type, loc_code).await? {
            None => return Ok(None),
            Some(i) => i,
        };

        if loc_item.flags.contains(&ItemFlag::DroppedItemsDontExpire) {
            return Ok(None);
        }

        ctx.trans.delete_item("possession", item_code).await?;

        Ok(None)
    }
}
pub static EXPIRE_ITEM_HANDLER: &'static (dyn TaskHandler + Sync + Send) = &ExpireItemTaskHandler;

pub async fn consider_expire_job_for_item(trans: &DBTrans, item: &Item) -> DResult<()> {
    let (loc_type, loc_code) = match item.location.split_once("/") {
        None => return Ok(()),
        Some(p) => p,
    };

    if loc_type != "room" {
        return Ok(());
    }

    let loc_item = match trans.find_item_by_type_code(loc_type, loc_code).await? {
        None => return Ok(()),
        Some(i) => i,
    };

    if loc_item.flags.contains(&ItemFlag::DroppedItemsDontExpire) {
        return Ok(());
    }

    trans
        .upsert_task(&Task {
            meta: TaskMeta {
                task_code: format!("{}/{}", item.item_type, item.item_code),
                next_scheduled: Utc::now() + chrono::Duration::hours(1),
                ..Default::default()
            },
            details: TaskDetails::ExpireItem {
                item_code: item.item_code.clone(),
            },
        })
        .await?;

    Ok(())
}

pub struct QueueHandler;
#[async_trait]
impl QueueCommandHandler for QueueHandler {
    async fn start_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<time::Duration> {
        if ctx.item.death_data.is_some() {
            user_error(
                "You try to drop it, but your ghostly hands slip through it uselessly".to_owned(),
            )?;
        }
        let item_id = match ctx.command {
            QueueCommand::Drop { possession_id } => possession_id,
            _ => user_error("Unexpected command".to_owned())?,
        };
        let item = match ctx
            .trans
            .find_item_by_type_code("possession", &item_id)
            .await?
        {
            None => user_error("Item not found".to_owned())?,
            Some(it) => it,
        };
        if item.location != format!("{}/{}", &ctx.item.item_type, &ctx.item.item_code) {
            user_error(format!(
                "You try to drop {} but realise you no longer have it",
                item.display_for_sentence(ctx.explicit().await?, 1, false)
            ))?
        }
        if item.action_type == LocationActionType::Worn {
            user_error(
                ansi!("You're wearing it - try using <bold>remove<reset> first").to_owned(),
            )?;
        }
        let msg = format!(
            "{} prepares to drop {}\n",
            &ctx.item.display_for_sentence(true, 1, true),
            &item.display_for_sentence(true, 1, false)
        );
        broadcast_to_room(ctx.trans, &ctx.item.location, None, &msg).await?;
        Ok(time::Duration::from_secs(1))
    }

    #[allow(unreachable_patterns)]
    async fn finish_command(&self, ctx: &mut QueuedCommandContext<'_>) -> UResult<()> {
        if ctx.item.death_data.is_some() {
            user_error(
                "You try to get it, but your ghostly hands slip through it uselessly".to_owned(),
            )?;
        }
        let item_id = match ctx.command {
            QueueCommand::Drop { possession_id } => possession_id,
            _ => user_error("Unexpected command".to_owned())?,
        };

        let item = match ctx
            .trans
            .find_item_by_type_code("possession", &item_id)
            .await?
        {
            None => user_error("Item not found".to_owned())?,
            Some(it) => it,
        };
        if item.location != format!("{}/{}", &ctx.item.item_type, &ctx.item.item_code) {
            user_error(format!(
                "You try to drop {} but realise you no longer have it!",
                &item.display_for_sentence(ctx.explicit().await?, 1, false)
            ))?
        }
        if item.action_type == LocationActionType::Worn {
            user_error(
                ansi!("You're wearing it - try using <bold>remove<reset> first").to_owned(),
            )?;
        }

        let possession_data = match item
            .possession_type
            .as_ref()
            .and_then(|pt| possession_data().get(&pt))
        {
            None => {
                user_error("That item no longer exists in the game so can't be handled".to_owned())?
            }
            Some(pd) => pd,
        };

        match check_item_ref_capacity(ctx.trans, &ctx.item.location, possession_data.weight).await?
        {
            CapacityLevel::AboveItemLimit => user_error(format!(
                "You can't drop {}, because it is so cluttered here there is no where to put it!",
                &item.display_for_sentence(ctx.explicit().await?, 1, false)
            ))?,
            _ => (),
        }

        let msg = format!(
            "{} drops {}\n",
            &ctx.item.display_for_sentence(true, 1, true),
            &item.display_for_sentence(true, 1, false)
        );
        broadcast_to_room(ctx.trans, &ctx.item.location, None, &msg).await?;
        let mut item_mut = (*item).clone();
        item_mut.location = ctx.item.location.clone();
        consider_expire_job_for_item(ctx.trans, &item_mut).await?;
        item_mut.action_type = LocationActionType::Normal;
        ctx.trans.save_item_model(&item_mut).await?;
        Ok(())
    }
}

pub struct Verb;
#[async_trait]
impl UserVerb for Verb {
    async fn handle(
        self: &Self,
        ctx: &mut VerbContext,
        _verb: &str,
        mut remaining: &str,
    ) -> UResult<()> {
        let player_item = get_player_item_or_fail(ctx).await?;

        let mut get_limit = Some(1);
        if remaining == "all" || remaining.starts_with("all ") {
            remaining = remaining[3..].trim();
            get_limit = None;
        } else if let (Some(n), remaining2) = parse_count(remaining) {
            get_limit = Some(n);
            remaining = remaining2;
        }
        let targets = search_items_for_user(
            ctx,
            &ItemSearchParams {
                include_contents: true,
                item_type_only: Some("possession"),
                limit: get_limit.unwrap_or(100),
                ..ItemSearchParams::base(&player_item, &remaining)
            },
        )
        .await?;

        if player_item.death_data.is_some() {
            user_error(
                "You try to drop it, but your ghostly hands slip through it uselessly".to_owned(),
            )?;
        }

        let mut player_item_mut = (*player_item).clone();
        for target in targets {
            if target.item_type != "possession" {
                user_error("You can't drop that!".to_owned())?;
            }
            queue_command(
                ctx,
                &mut player_item_mut,
                &QueueCommand::Drop {
                    possession_id: target.item_code.clone(),
                },
            )
            .await?;
        }
        ctx.trans.save_item_model(&player_item_mut).await?;
        Ok(())
    }
}
static VERB_INT: Verb = Verb;
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;