use anyhow::Result;
use clap::{Args, Subcommand};

use super::macros::{define_delete_command, define_get_command};
use super::parse_created_at;
use super::print_json;
use crate::domain::brews::{QuickNote, UpdateBrew};
use crate::domain::ids::{BagId, BrewId, GearId};
use crate::infrastructure::client::BrewlogClient;

#[derive(Debug, Subcommand)]
pub enum BrewCommands {
    /// Add a new brew
    Add(AddBrewCommand),
    /// List all brews
    List(ListBrewsCommand),
    /// Get a brew by ID
    Get(GetBrewCommand),
    /// Update a brew
    Update(UpdateBrewCommand),
    /// Delete a brew
    Delete(DeleteBrewCommand),
}

pub async fn run(client: &BrewlogClient, cmd: BrewCommands) -> Result<()> {
    match cmd {
        BrewCommands::Add(c) => add_brew(client, c).await,
        BrewCommands::List(c) => list_brews(client, c).await,
        BrewCommands::Get(c) => get_brew(client, c).await,
        BrewCommands::Update(c) => update_brew(client, c).await,
        BrewCommands::Delete(c) => delete_brew(client, c).await,
    }
}

#[derive(Debug, Args)]
pub struct AddBrewCommand {
    /// ID of the bag to brew from
    #[arg(long)]
    pub bag_id: i64,

    /// Amount of coffee in grams
    #[arg(long, default_value = "15.0")]
    pub coffee_weight: f64,

    /// ID of the grinder to use
    #[arg(long)]
    pub grinder_id: i64,

    /// Grind setting (e.g., 6.0 or 7.5)
    #[arg(long, default_value = "6.0")]
    pub grind_setting: f64,

    /// ID of the brewer to use
    #[arg(long)]
    pub brewer_id: i64,

    /// ID of the filter paper to use (optional)
    #[arg(long)]
    pub filter_paper_id: Option<i64>,

    /// Volume of water in ml
    #[arg(long, default_value = "250")]
    pub water_volume: i32,

    /// Water temperature in Celsius
    #[arg(long, default_value = "91.0")]
    pub water_temp: f64,

    /// Quick notes (comma-separated: good,too-fast,too-slow,too-hot,under-extracted,over-extracted)
    #[arg(long, value_delimiter = ',')]
    pub quick_notes: Vec<String>,

    /// Brew time in seconds (e.g., 150 for 2:30)
    #[arg(long)]
    pub brew_time: Option<i32>,

    /// Override creation timestamp (e.g. 2025-08-05T10:00:00Z or 2025-08-05)
    #[arg(long)]
    pub created_at: Option<String>,
}

pub async fn add_brew(client: &BrewlogClient, command: AddBrewCommand) -> Result<()> {
    let quick_notes: Vec<QuickNote> = command
        .quick_notes
        .iter()
        .filter_map(|s| QuickNote::from_str_value(s))
        .collect();
    let created_at = command
        .created_at
        .map(|s| parse_created_at(&s))
        .transpose()?;

    let brew = client
        .brews()
        .create(
            BagId::new(command.bag_id),
            command.coffee_weight,
            GearId::new(command.grinder_id),
            command.grind_setting,
            GearId::new(command.brewer_id),
            command.filter_paper_id.map(GearId::new),
            command.water_volume,
            command.water_temp,
            quick_notes,
            command.brew_time,
            created_at,
        )
        .await?;
    print_json(&brew)
}

#[derive(Debug, Args)]
pub struct ListBrewsCommand {
    /// Filter by bag ID
    #[arg(long)]
    pub bag_id: Option<i64>,
}

pub async fn list_brews(client: &BrewlogClient, command: ListBrewsCommand) -> Result<()> {
    let brews = client.brews().list(command.bag_id.map(BagId::new)).await?;
    print_json(&brews)
}

#[derive(Debug, Args)]
pub struct UpdateBrewCommand {
    #[arg(long)]
    pub id: i64,

    /// ID of the bag to brew from
    #[arg(long)]
    pub bag_id: Option<i64>,

    /// Amount of coffee in grams
    #[arg(long)]
    pub coffee_weight: Option<f64>,

    /// ID of the grinder to use
    #[arg(long)]
    pub grinder_id: Option<i64>,

    /// Grind setting (e.g., 6.0 or 7.5)
    #[arg(long)]
    pub grind_setting: Option<f64>,

    /// ID of the brewer to use
    #[arg(long)]
    pub brewer_id: Option<i64>,

    /// ID of the filter paper to use
    #[arg(long)]
    pub filter_paper_id: Option<i64>,

    /// Volume of water in ml
    #[arg(long)]
    pub water_volume: Option<i32>,

    /// Water temperature in Celsius
    #[arg(long)]
    pub water_temp: Option<f64>,

    /// Quick notes (comma-separated: good,too-fast,too-slow,too-hot,under-extracted,over-extracted)
    #[arg(long, value_delimiter = ',')]
    pub quick_notes: Option<Vec<String>>,

    /// Brew time in seconds (e.g., 150 for 2:30)
    #[arg(long)]
    pub brew_time: Option<i32>,

    /// Override creation timestamp (e.g. 2025-08-05T10:00:00Z or 2025-08-05)
    #[arg(long)]
    pub created_at: Option<String>,
}

pub async fn update_brew(client: &BrewlogClient, command: UpdateBrewCommand) -> Result<()> {
    let created_at = command
        .created_at
        .map(|s| parse_created_at(&s))
        .transpose()?;
    let quick_notes = command.quick_notes.map(|notes| {
        notes
            .iter()
            .filter_map(|s| QuickNote::from_str_value(s))
            .collect()
    });
    let payload = UpdateBrew {
        bag_id: command.bag_id.map(BagId::new),
        coffee_weight: command.coffee_weight,
        grinder_id: command.grinder_id.map(GearId::new),
        grind_setting: command.grind_setting,
        brewer_id: command.brewer_id.map(GearId::new),
        filter_paper_id: command.filter_paper_id.map(GearId::new),
        water_volume: command.water_volume,
        water_temp: command.water_temp,
        quick_notes,
        brew_time: command.brew_time,
        created_at,
    };

    let brew = client
        .brews()
        .update(BrewId::new(command.id), &payload)
        .await?;
    print_json(&brew)
}

define_get_command!(GetBrewCommand, get_brew, BrewId, brews);
define_delete_command!(DeleteBrewCommand, delete_brew, BrewId, brews, "brew");
