use std::borrow::Cow;

use super::params::{
    AccountId, AccountingMethod, AgingMethod, ArPaid, AttachmentType, Cleared, CustomerId,
    DateMacro, HasValue, ItemId, Printed, SortOrder, SummarizeColumnBy, TermId, VendorId,
};
use chrono::NaiveDate;

/// Represents parameters for `QuickBooks` reports.
pub trait QBReportParams {
    fn params(&self) -> impl Iterator<Item = (&'static str, Cow<'_, str>)>;
    fn to_query_string(&self) -> String {
        self.params()
            .map(|(name, value)| format!("{name}={value}"))
            .collect::<Vec<_>>()
            .join("&")
    }
}

/// Represents a type of `QuickBooks` report.
pub trait QBReportType {
    type QueryParams: QBReportParams;
    fn url_name(&self) -> &'static str;
}

use paste::paste;

macro_rules! impl_report_type {
  ($(
    $report_ty:ident, $url_name:expr, [$($param:tt),* $(,)?]; $(($doc:expr))?
  )*
  $(;)?) => {
    $(

    paste! {
      #[doc = "Type to represent the `" $report_ty "` report in quickbooks:\n\nAPI Reference:\n<https://developer.intuit.com/app/developer/qbo/docs/api/accounting/report-entities/" [<$report_ty:lower>] ">\n\n" $($doc)?]
      pub struct $report_ty;

      impl QBReportType for $report_ty {
        type QueryParams = [<$report_ty Params>];
        fn url_name(&self) -> &'static str {
          $url_name
        }
      }

      #[derive(Debug, Default)]
      #[allow(non_snake_case)]
      #[doc = "Parameters for the `" $report_ty "` report.\n\n" $($doc)?]
      pub struct [<$report_ty Params>] {
        $(
        pub $param: Option<impl_report_type!(@param_type $param)>,
        )*
      }

      impl [<$report_ty Params>] {
        #[must_use]
        pub fn new() -> Self {
          Self {
            $(
            $param: None,
            )*
          }
        }

        $(
          impl_report_type!(@param_method $param);
        )*

        fn iter_params(&self) -> impl Iterator<Item = (&'static str, Option<Cow<'_, str>>)> {
          [
            $(
              (stringify!($param), self.$param.as_ref().map(|p| p.value())),
            )*
          ]
          .into_iter()
        }
      }

      impl QBReportParams for [<$report_ty Params>] {
        fn params(&self) -> impl Iterator<Item = (&'static str, Cow<'_, str>)> {
          self.iter_params().filter_map(|(name, value)| {
            value.map(|v| (name, v))
          })
        }
      }
    }
    )+
  };

  // Generic handler for vector parameters
  (@param_method_vec $param:tt, $id_type:ty) => {
    #[must_use]
    pub fn $param(mut self, param: impl Into<$id_type>) -> Self {
      if let Some(ref mut vec) = self.$param {
        vec.push(param.into());
      } else {
        self.$param = Some(vec![param.into()]);
      }
      self
    }

    paste! {
      #[must_use]
      pub fn [<$param s>](mut self, params: Vec<$id_type>) -> Self {
        self.$param = Some(params);
        self
      }
    }
  };

  // Apply vector method for each vector parameter
  (@param_method vendor) => { impl_report_type!(@param_method_vec vendor, VendorId); };
  (@param_method customer) => { impl_report_type!(@param_method_vec customer, CustomerId); };
  (@param_method term) => { impl_report_type!(@param_method_vec term, TermId); };
  (@param_method item) => { impl_report_type!(@param_method_vec item, ItemId); };
  (@param_method account) => { impl_report_type!(@param_method_vec account, AccountId); };
  (@param_method source_account) => { impl_report_type!(@param_method_vec source_account, AccountId); };
  // Special case for columns as it ends with an 's' yet is also a vector
  (@param_method columns) => {
    #[must_use]
    pub fn columns(mut self, params: Vec<String>) -> Self {
      self.columns = Some(params);
      self
    }

    #[must_use]
    pub fn column(mut self, param: impl Into<String>) -> Self {
      if let Some(ref mut vec) = self.columns {
        vec.push(param.into());
      } else {
        self.columns = Some(vec![param.into()]);
      }
      self
    }
  };

  // Simple setter for non-vector parameters
  (@param_method $param:tt) => {
    #[must_use]
    pub fn $param(mut self, param: impl Into<impl_report_type!(@param_type $param)>) -> Self {
      self.$param = Some(param.into());
      self
    }
  };

  // Helper macro to map parameter names to appropriate types
  (@param_type vendor) => { Vec<VendorId> };
  (@param_type customer) => { Vec<CustomerId> };
  (@param_type term) => { Vec<TermId> };
  (@param_type item) => { Vec<ItemId> };
  (@param_type account) => { Vec<AccountId> };
  (@param_type columns) => { Vec<String> };
  (@param_type source_account) => { Vec<AccountId> };
  (@param_type accounting_method) => { AccountingMethod };
  (@param_type date_macro) => { DateMacro };
  (@param_type start_date) => { NaiveDate };
  (@param_type end_date) => { NaiveDate };
  (@param_type summarize_column_by) => { SummarizeColumnBy };
  (@param_type as_of_date) => { NaiveDate };
  (@param_type aging_method) => { AgingMethod };
  (@param_type arpaid) => { ArPaid };
  (@param_type qzurl) => { String };
  (@param_type department) => { String };
  (@param_type report_date) => { NaiveDate };
  (@param_type sort_order) => { SortOrder };
  (@param_type shipvia) => { String };
  (@param_type end_duedate) => { NaiveDate };
  (@param_type start_duedate) => { NaiveDate };
  (@param_type custom1) => { String };
  (@param_type custom2) => { String };
  (@param_type custom3) => { String };
  (@param_type num_periods) => { u32 };
  (@param_type past_due) => { String };
  (@param_type aging_period) => { String };
  (@param_type adjusted_gain_loss) => { String };
  (@param_type class) => { String };
  (@param_type sort_by) => { String };
  (@param_type attachment_type) => { AttachmentType };
  (@param_type with_qbo_identifier) => { bool };
  (@param_type add_due_date) => { String };
  (@param_type account_type) => { String };
  (@param_type end_svcdate) => { NaiveDate };
  (@param_type svcdate_macro) => { DateMacro };
  (@param_type start_svcdate) => { NaiveDate };
  (@param_type group_by) => { String };
  (@param_type payment_method) => { String };
  (@param_type employee) => { String };
  (@param_type agency_id) => { String };
  (@param_type duedate_macro) => { DateMacro };
  (@param_type bothamount) => { String };
  (@param_type transaction_type) => { String };
  (@param_type docnum) => { String };
  (@param_type start_moddate) => { NaiveDate };
  (@param_type source_account_type) => { String };
  (@param_type start_createdate) => { NaiveDate };
  (@param_type memo) => { String };
  (@param_type appaid) => { String };
  (@param_type moddate_macro) => { String };
  (@param_type printed) => { Printed };
  (@param_type createdate_macro) => { String };
  (@param_type cleared) => { Cleared };
  (@param_type end_createdate) => { NaiveDate };
  (@param_type name) => { String };
  (@param_type end_moddate) => { NaiveDate };

  (@param_type $param:tt) => { compile_error!(
    "Unsupported parameter type for report"
  ) };

  () => {}
}

impl_report_type!(
  AccountListDetail, "AccountList", [
      accounting_method,
      date_macro,
      start_date,
      end_date,
      summarize_column_by,
  ]; ("List of accounts with details")

  APAgingDetail, "AgedPayableDetail", [
    as_of_date,
    aging_method,
    vendor,
    columns
  ];

  APAgingSummary, "AgedPayables", [
    customer,
    qzurl,
    vendor,
    date_macro,
    department,
    report_date,
    sort_order,
    aging_method
  ];

  ARAgingDetail, "AgedReceivableDetail", [
    customer,
    shipvia,
    term,
    end_duedate,
    start_duedate,
    custom1,
    custom2,
    custom3,
    report_date,
    num_periods,
    aging_method,
    past_due,
    aging_period,
    columns
  ];

  ARAgingSummary, "AgedReceivables", [
    customer,
    qzurl,
    date_macro,
    aging_method,
    report_date,
    sort_order,
    department
  ];

  BalanceSheet, "BalanceSheet", [
    customer,
    qzurl,
    end_date,
    accounting_method,
    date_macro,
    adjusted_gain_loss,
    class,
    item,
    sort_order,
    summarize_column_by,
    department,
    vendor,
    start_date
  ];

  CashFlow, "CashFlow", [
    customer,
    vendor,
    end_date,
    date_macro,
    class,
    item,
    sort_order,
    summarize_column_by,
    department,
    start_date
  ];

  CustomerBalance, "CustomerBalance", [
    customer,
    accounting_method,
    date_macro,
    arpaid,
    report_date,
    sort_order,
    summarize_column_by,
    department
  ];

  CustomerBalanceDetail, "CustomerBalanceDetail", [
    customer,
    shipvia,
    term,
    end_duedate,
    start_duedate,
    custom1,
    custom2,
    custom3,
    arpaid,
    report_date,
    sort_order,
    aging_method,
    department
  ];

  CustomerIncome, "CustomerIncome", [
    customer,
    term,
    accounting_method,
    end_date,
    date_macro,
    class,
    sort_order,
    summarize_column_by,
    department,
    start_date,
    vendor
  ];

  FECReport, "FECReport", [
    attachment_type,
    with_qbo_identifier,
    start_date,
    end_date,
    add_due_date
  ];

  GeneralLedger, "GeneralLedger", [
    customer,
    account,
    accounting_method,
    source_account,
    end_date,
    date_macro,
    account_type,
    sort_by,
    sort_order,
    start_date,
    summarize_column_by,
    class,
    item,
    department,
    vendor,
    columns
  ];

  GeneralLedgerFR, "GeneralLedgerFR", [
    customer,
    account,
    accounting_method,
    source_account,
    end_date,
    date_macro,
    account_type,
    sort_by,
    sort_order,
    start_date,
    summarize_column_by,
    class,
    vendor
  ];

  InventoryValuationDetail, "InventoryValuationDetail", [
    end_date,
    end_svcdate,
    date_macro,
    svcdate_macro,
    start_svcdate,
    group_by,
    start_date,
    columns
  ];

  InventoryValuationSummary, "InventoryValuationSummary", [
    qzurl,
    date_macro,
    item,
    report_date,
    sort_order,
    summarize_column_by
  ];

  JournalReport, "JournalReport", [
    end_date,
    date_macro,
    sort_by,
    sort_order,
    start_date,
    columns
  ];

  ProfitAndLoss, "ProfitAndLoss", [
    customer,
    qzurl,
    accounting_method,
    end_date,
    date_macro,
    adjusted_gain_loss,
    class,
    item,
    sort_order,
    summarize_column_by,
    department,
    vendor,
    start_date
  ];

  ProfitAndLossDetail, "ProfitAndLossDetail", [
    customer,
    account,
    accounting_method,
    end_date,
    date_macro,
    adjusted_gain_loss,
    class,
    sort_by,
    payment_method,
    sort_order,
    employee,
    department,
    vendor,
    account_type,
    start_date,
    columns
  ];

  SalesByClassSummary, "ClassSales", [
    customer,
    accounting_method,
    end_date,
    date_macro,
    class,
    item,
    summarize_column_by,
    department,
    start_date
  ];

  SalesByCustomer, "CustomerSales", [
    customer,
    qzurl,
    accounting_method,
    end_date,
    date_macro,
    class,
    item,
    sort_order,
    summarize_column_by,
    department,
    start_date
  ];

  SalesByDepartment, "DepartmentSales", [
    customer,
    accounting_method,
    end_date,
    date_macro,
    class,
    item,
    sort_order,
    summarize_column_by,
    department,
    start_date
  ];

  SalesByProduct, "ItemSales", [
    customer,
    end_duedate,
    accounting_method,
    end_date,
    date_macro,
    start_duedate,
    class,
    item,
    sort_order,
    summarize_column_by,
    department,
    start_date
  ];

  TaxSummary, "TaxSummary", [
    agency_id,
    accounting_method,
    end_date,
    date_macro,
    sort_order,
    start_date
  ];

  TransactionList, "TransactionList", [
    date_macro,
    payment_method,
    duedate_macro,
    arpaid,
    bothamount,
    transaction_type,
    docnum,
    start_moddate,
    source_account_type,
    group_by,
    start_date,
    department,
    start_duedate,
    columns,
    end_duedate,
    vendor,
    end_date,
    memo,
    appaid,
    moddate_macro,
    printed,
    createdate_macro,
    cleared,
    customer,
    qzurl,
    term,
    end_createdate,
    name,
    sort_by,
    sort_order,
    start_createdate,
    end_moddate
  ];

  TransactionListByCustomer, "TransactionListByCustomer", [
    date_macro,
    payment_method,
    duedate_macro,
    arpaid,
    bothamount,
    transaction_type,
    docnum,
    start_moddate,
    source_account_type,
    group_by,
    start_date,
    department,
    start_duedate,
    columns,
    end_duedate,
    end_date,
    memo,
    appaid,
    moddate_macro,
    printed,
    createdate_macro,
    cleared,
    customer,
    qzurl,
    term,
    end_createdate,
    name,
    sort_by,
    sort_order,
    start_createdate,
    end_moddate
  ];

  TransactionListByVendor, "TransactionListByVendor", [
    date_macro,
    payment_method,
    duedate_macro,
    arpaid,
    bothamount,
    transaction_type,
    docnum,
    start_moddate,
    source_account_type,
    group_by,
    start_date,
    department,
    start_duedate,
    columns,
    end_duedate,
    vendor,
    end_date,
    memo,
    appaid,
    moddate_macro,
    printed,
    createdate_macro,
    cleared,
    qzurl,
    term,
    end_createdate,
    name,
    sort_by,
    sort_order,
    start_createdate,
    end_moddate
  ];

  TransactionListWithSplits, "TransactionListWithSplits", [
    docnum,
    name,
    end_date,
    date_macro,
    payment_method,
    source_account_type,
    transaction_type,
    group_by,
    sort_by,
    sort_order,
    start_date,
    columns
  ];

  TrialBalance, "TrialBalance", [
    accounting_method,
    end_date,
    date_macro,
    sort_order,
    summarize_column_by,
    start_date
  ];

  VendorBalance, "VendorBalance", [
    qzurl,
    accounting_method,
    date_macro,
    appaid,
    report_date,
    sort_order,
    summarize_column_by,
    department,
    vendor
  ];

  VendorBalanceDetail, "VendorBalanceDetail", [
    term,
    accounting_method,
    date_macro,
    appaid,
    report_date,
    sort_order,
    summarize_column_by,
    department,
    vendor,
    columns,
    duedate_macro,
    start_duedate,
    end_duedate
  ];

  VendorExpenses, "VendorExpenses", [
    customer,
    vendor,
    end_date,
    date_macro,
    class,
    sort_order,
    summarize_column_by,
    department,
    accounting_method,
    start_date
  ];
);
