use std::str::FromStr;

use stripe::{
	CheckoutSession, CheckoutSessionId, CheckoutSessionPaymentStatus, Client,
	CreateCheckoutSession, CreateCheckoutSessionLineItems, CreateCheckoutSessionLineItemsPriceData,
	CreateCheckoutSessionSubscriptionData, CreateCustomer, Customer, CustomerId, StripeError,
	Subscription, SubscriptionId,
};
const SUBSCRIPTION_LIMIT_LIST: u64 = 80;
/// ユーザー一人が同時に持つことができるサブスクの最大数
pub const SUBSCRIPTION_LIMIT: u64 = SUBSCRIPTION_LIMIT_LIST / 2;
/// チェックアウトセッションIDのプレースホルダー
pub const CHECKOUT_SESSION_ID: &str = "{CHECKOUT_SESSION_ID}";
/// 通貨の型エイリアス
pub type Currency = stripe::Currency;
/// インターバルの型エイリアス
pub type Interval = stripe::CreateCheckoutSessionLineItemsPriceDataRecurringInterval;
/// インターバルとオプションを保持する構造体
#[derive(Clone, Default)]
pub struct IntervalWithOptions {
	/// インターバル
	pub interval: Interval,
	/// トライアル期間（日数）
	pub trial_period_days: Option<u32>,
}
/// 単発購入用
pub struct StripeOrder {
	/// 商品名
	pub name: String,
	/// 商品ID
	pub id: String,
	/// 通貨
	pub currency: Currency,
	/// 金額
	pub price: i64,
	/// 個数
	pub quantity: u64,
}

/// ストライプの処理をカプセル化する構造体
#[derive(Clone)]
pub struct Stripe {
	client: Client,
}

impl Stripe {
	/// コンストラクタ
	/// シークレットキーを受け取り、Stripeクライアントを初期化します
	pub fn new(secret_key: impl Into<String>) -> Self {
		let client = Client::new(secret_key.into());
		Self { client }
	}
	/// 顧客を作成します
	pub async fn create_customer(
		&self,
		name: &str,
		email: &str,
		id: &str,
	) -> Result<Customer, StripeError> {
		Customer::create(
			&self.client,
			CreateCustomer {
				name: Some(name),
				email: Some(email),
				metadata: Some(std::collections::HashMap::from([(
					String::from("id"),
					String::from(id),
				)])),
				..Default::default()
			},
		)
		.await
	}
	/*c
	/// 商品を作成します
	pub async fn create_product(&self, name: &str) -> Result<Product, StripeError> {
		let mut create_product = CreateProduct::new(name);
		create_product.metadata = Some(std::collections::HashMap::from([(
			String::from("async-stripe"),
			String::from("true"),
		)]));
		let product = Product::create(&self.client, create_product).await?;
		Ok(product)
	}
	/// 価格を作成します
	pub async fn create_price(
		&self,
		product_id: &str,
		price: i64,
		currency: Currency,
		interval: Option<IntervalWithOptions>,
	) -> Result<Price, StripeError> {
		let mut create_price = CreatePrice::new(currency);
		create_price.product = Some(IdOrCreate::Id(product_id));
		create_price.unit_amount = Some(price);
		// recurring を設定
		if let Some(interval) = interval {
			create_price.recurring = Some(CreatePriceRecurring {
				interval: interval.interval.clone(),
				trial_period_days: interval.trial_period_days,
				..Default::default()
			});
		}
		create_price.expand = &["product"];
		Price::create(&self.client, create_price).await
	}
	*/
	/// 単発のチェックアウトセッションを作成します(商品を管理しません)
	pub async fn create_payment_session(
		&self,
		// 既存Customerがあるなら紐付け
		customer_id: Option<&str>,
		// 自前注文ID等（任意だが推奨）
		client_reference_id: Option<&str>,
		// items 名前, id, 通貨, 金額, 個数
		items: Vec<StripeOrder>,
		url_success: &str,
		url_cancel: &str,
	) -> Result<CheckoutSession, StripeError> {
		let mut params = CreateCheckoutSession::new();
		params.success_url = Some(url_success);
		params.cancel_url = Some(url_cancel);

		params.customer = customer_id
			.map(|id| CustomerId::from_str(id))
			.transpose()
			.map_err(|e| StripeError::ClientError(e.to_string()))?;

		// 自前注文ID等を入れる（Webhookで突合するため）
		params.client_reference_id = client_reference_id;

		params.mode = Some(stripe::CheckoutSessionMode::Payment);

		params.line_items = Some(
			items
				.iter()
				.map(|item| CreateCheckoutSessionLineItems {
					quantity: Some(item.quantity),
					price_data: Some(stripe::CreateCheckoutSessionLineItemsPriceData {
						currency: item.currency,
						unit_amount: Some(item.price),
						product_data: Some(
							stripe::CreateCheckoutSessionLineItemsPriceDataProductData {
								name: item.name.clone(),
								description: Some(item.id.clone()),
								metadata: Some(std::collections::HashMap::from([(
									String::from("id"),
									String::from(item.id.clone()),
								)])),
								..Default::default()
							},
						),
						..Default::default()
					}),
					..Default::default()
				})
				.collect(),
		);

		// subscription の検証に欲しい参照を expand
		params.expand = &["payment_intent", "customer", "line_items"];

		CheckoutSession::create(&self.client, params).await
	}
	/// サブスクのチェックアウトセッションを作成します
	pub async fn create_subscription_session(
		&self,
		// 既存Customerを必ず紐づける（安全策）
		customer_id: &str,
		// 自前注文ID等（任意だが推奨）
		client_reference_id: Option<&str>,
		item: StripeOrder,
		interval: Interval,
		url_success: &str,
		url_cancel: &str,
	) -> Result<CheckoutSession, StripeError> {
		let mut params = CreateCheckoutSession::new();
		params.success_url = Some(url_success);
		params.cancel_url = Some(url_cancel);

		params.customer = Some(
			CustomerId::from_str(customer_id)
				.map_err(|e| StripeError::ClientError(e.to_string()))?,
		);

		// 自前注文ID等を入れる（Webhookで突合するため）
		params.client_reference_id = client_reference_id;

		params.mode = Some(stripe::CheckoutSessionMode::Subscription);

		params.line_items = Some(
			[CreateCheckoutSessionLineItems {
				quantity: Some(1),
				price_data: Some(CreateCheckoutSessionLineItemsPriceData {
					currency: Currency::JPY,
					unit_amount: Some(item.price),
					product_data: Some(
						stripe::CreateCheckoutSessionLineItemsPriceDataProductData {
							name: item.name.clone(),
							description: Some(item.id.clone()),
							metadata: Some(std::collections::HashMap::from([(
								String::from("id"),
								String::from(item.id.clone()),
							)])),
							..Default::default()
						},
					),
					recurring: Some(stripe::CreateCheckoutSessionLineItemsPriceDataRecurring {
						interval: interval,
						..Default::default()
					}),
					..Default::default()
				}),
				..Default::default()
			}]
			.to_vec(),
		);
		params.subscription_data = Some(CreateCheckoutSessionSubscriptionData {
			metadata: Some(std::collections::HashMap::from([(
				String::from("id"),
				String::from(item.id.clone()),
			)])),
			..Default::default()
		});

		// subscription の検証に欲しい参照を expand
		params.expand = &["subscription", "customer", "line_items.data.price"];

		CheckoutSession::create(&self.client, params).await
	}
	/// 指定された session_id が支払い完了かどうかを返す
	/// 単発の場合⇒そのまま権利付与して良い
	/// サブスクの場合⇒初回の支払いは上手くいっているので権利付与して良い
	pub async fn check_payment_complete(&self, session_id: &str) -> Result<bool, StripeError> {
		let session_id = CheckoutSessionId::from_str(session_id)
			.map_err(|e| StripeError::ClientError(e.to_string()))?;
		let session = CheckoutSession::retrieve(&self.client, &session_id, &[]).await?;
		Ok(match session.payment_status {
			CheckoutSessionPaymentStatus::Paid
			| CheckoutSessionPaymentStatus::NoPaymentRequired => true,
			_ => false,
		})
	}

	/// 指定された subscription_id が権利付与するべき状態かどうかを返す
	pub async fn check_subscription_entitled(
		&self,
		subscription_id: &str,
	) -> Result<bool, StripeError> {
		// subscription_id を SubscriptionId に変換
		let subscription_id = SubscriptionId::from_str(subscription_id)
			.map_err(|e| StripeError::ClientError(e.to_string()))?;
		// subscription_id を使用して、Subscription を取得
		let subscription = Subscription::retrieve(&self.client, &subscription_id, &[]).await?;
		// 現在時刻を取得
		let now = std::time::SystemTime::now()
			.duration_since(std::time::SystemTime::UNIX_EPOCH)
			.map_err(|e| StripeError::ClientError(e.to_string()))?
			.as_secs() as i64;
		// subscription の status が Active または Trialing であるかを確認
		let status_ok = match subscription.status {
			stripe::SubscriptionStatus::Active | stripe::SubscriptionStatus::Trialing => true,
			_ => false,
		};
		// subscription の current_period_end が現在時刻より後であるかを確認
		let period_ok = subscription.current_period_end > now;
		Ok(status_ok && period_ok)
	}

	/// 指定した customer_id に紐づくサブスクをすべて取得します
	/// Active, Trialing, Unpaid, PastDue を非同期に取得して合算します
	/// この合計が SUBSCRIPTION_LIMIT を超えていたら新規サブスク契約を許さず、既存の契約をキャンセルするように促してください
	pub async fn list_subscriptions_by_customer(
		&self,
		customer_id: &str,
	) -> Result<Vec<Subscription>, StripeError> {
		let customer_id = CustomerId::from_str(customer_id)
			.map_err(|e| StripeError::ClientError(e.to_string()))?;

		let (s1, s2, s3, s4) = tokio::try_join!(
			self.list_subscriptions_by_status(
				&customer_id,
				stripe::SubscriptionStatusFilter::Active
			),
			self.list_subscriptions_by_status(
				&customer_id,
				stripe::SubscriptionStatusFilter::Trialing
			),
			self.list_subscriptions_by_status(
				&customer_id,
				stripe::SubscriptionStatusFilter::Unpaid
			),
			self.list_subscriptions_by_status(
				&customer_id,
				stripe::SubscriptionStatusFilter::PastDue
			),
		)?;

		let mut all = s1;
		all.extend(s2);
		all.extend(s3);
		all.extend(s4);

		Ok(all)
	}

	/// 指定したステータスのサブスクを取得する内部ヘルパー
	async fn list_subscriptions_by_status(
		&self,
		customer_id: &CustomerId,
		status: stripe::SubscriptionStatusFilter,
	) -> Result<Vec<Subscription>, StripeError> {
		let mut params = stripe::ListSubscriptions::new();
		params.customer = Some(customer_id.clone());
		params.limit = Some(SUBSCRIPTION_LIMIT_LIST);
		params.status = Some(status);

		Ok(Subscription::list(&self.client, &params).await?.data)
	}

	/// サブスクを「期間末で解約」します（一般的な解約）
	pub async fn cancel_subscription_at_period_end(
		&self,
		subscription_id: &str,
	) -> Result<Subscription, StripeError> {
		let subscription_id = SubscriptionId::from_str(subscription_id)
			.map_err(|e| StripeError::ClientError(e.to_string()))?;

		let mut params = stripe::UpdateSubscription::new();
		params.cancel_at_period_end = Some(true);

		Subscription::update(&self.client, &subscription_id, params).await
	}
}
#[cfg(test)]
mod test {
	use super::*;
	use dotenvy;
	fn create_stripe() -> Stripe {
		dotenvy::from_filename("secret/.env").unwrap();
		Stripe::new(std::env::var("STRIPE_SECRET_KEY").unwrap())
	}
	#[tokio::test]
	async fn test_create_customer() {
		// Note: tests will fail if STRIPE_SECRET_KEY is not set
		let stripe = create_stripe();
		let customer = stripe
			.create_customer("test", "test@example.com", "desc")
			.await;
		// Check if environment variables are set, otherwise this unwrap might panic earlier
		assert_eq!(
			match customer {
				Ok(_) => Default::default(),
				Err(e) => e.to_string(),
			},
			"".to_string()
		);
	}

	#[tokio::test]
	async fn test_payment() {
		let stripe = create_stripe();

		// Checkout Session を作る
		let session = stripe
			.create_payment_session(
				Some("cus_TbZUnFeAibI4cz"),
				None,
				vec![
					StripeOrder {
						name: "テスト10円ガム".to_string(),
						id: "019bb2ff-fa86-738b-8adc-b7f0354a23c6".to_string(),
						currency: Currency::JPY,
						price: 10,
						quantity: 2,
					},
					StripeOrder {
						name: "テストガリガリ君".to_string(),
						id: "019bb2ff-fa86-738b-8adc-b7f0354a23c6".to_string(),
						currency: Currency::JPY,
						price: 100,
						quantity: 1,
					},
				],
				&format!("http://example.com/success?session_id={CHECKOUT_SESSION_ID}"),
				&format!("http://example.com/cancel?session_id={CHECKOUT_SESSION_ID}"),
			)
			.await;

		eprintln!("Checkout URL: {}", session.unwrap().url.unwrap());
	}

	#[tokio::test]
	async fn test_subscription() {
		let stripe = create_stripe();
		let session = stripe
			.create_subscription_session(
				"cus_TbZUnFeAibI4cz",
				None,
				StripeOrder {
					name: "ぷっちょサブスク".to_string(),
					id: "019bb2ff-fa86-738b-8adc-b7f0354a23c6".to_string(),
					currency: Currency::JPY,
					price: 100,
					quantity: 1,
				},
				Interval::Month,
				&format!("http://example.com/success?session_id={CHECKOUT_SESSION_ID}"),
				&format!("http://example.com/cancel?session_id={CHECKOUT_SESSION_ID}"),
			)
			.await;

		eprintln!("Checkout URL: {}", session.unwrap().url.unwrap());
	}

	#[tokio::test]
	async fn test_check_payment_complete() {
		let stripe = create_stripe();
		let completed = stripe
			.check_payment_complete(
				"cs_live_b1KN4Pjfm7tNtWFUzuotfaxe2c7iedzskD0LR3UKYTn8OMLtfS1gtkscTU",
			)
			.await
			.unwrap();
		assert!(completed);
	}

	#[tokio::test]
	async fn test_check_subscription_entitled() {
		let stripe = create_stripe();
		let entitled = stripe
			.check_subscription_entitled("sub_1Sk9cQJQppDTAopgbH8ovXQg")
			.await
			.unwrap();
		assert!(entitled);
	}
	#[tokio::test]
	async fn test_list_subscriptions_by_customer() {
		let stripe = create_stripe();
		let subscriptions = stripe
			.list_subscriptions_by_customer("cus_TbZUnFeAibI4cz")
			.await
			.unwrap();
		for i in &subscriptions {
			eprintln!("{:?}", i);
			eprintln!("{:?}", i.metadata.get("id").cloned().unwrap_or_default());
		}
		assert!(!subscriptions.is_empty());
	}
	#[tokio::test]
	async fn test_cancel_subscription_at_period_end() {
		let stripe = create_stripe();
		let subscription = stripe
			.cancel_subscription_at_period_end("sub_1Sk9cQJQppDTAopgbH8ovXQg")
			.await
			.unwrap();
		assert!(subscription.cancel_at_period_end);
	}
}
