import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Period, Monthly } from '@financial-times/n-pricing';

import {
	getDurationFromISO8601Value,
	is52WeeksOrLonger,
	is90DaysOrLonger,
} from '../helpers';

export function PaymentTerm({
	fieldId = 'paymentTermField',
	inputName = 'paymentTerm',
	isPrintOrBundle = false,
	isDigitalEdition = false,
	options = [],
	showLegal = true,
	largePrice = false,
	optionsInARow = false,
	billingCountry = '',
	isAutoRenewingSubscriptionTermType = true,
	isNonRenewingSubscriptionTermType = false,
}) {
	/**
	 * Capitalises a string.
	 * @param {string} Input string to be capitalised
	 * @returns {string}
	 */
	const capitalise = (string) =>
		Boolean(string) ? string[0].toUpperCase() + string.slice(1) : string;

	/**
	 * Creates the JSX for a single payment-term option, including the input,
	 * title, discount messaging, and descriptive pricing copy.
	 *
	 * @param {Object} option - Payment term configuration
	 * @param {string} option.value - ISO 8601 duration of the subscription term of the offer
	 * @param {string} option.name - term name, e.g. "monthly", "annual", "2 yearly
	 * @param {string} option.price - Formatted display price
	 * @param {string|number} [option.amount] - Price expressed in numerical terms
	 * @param {string} [option.symbol] - Currency symbol, e.g. £
	 * @param {string} [option.monthlyPrice] - Precomputed monthly equivalent price (can be with or without currency symbol)
	 * @param {boolean} [option.discount] - Whether the option should display discount messaging
	 * @param {boolean} [option.bestOffer] - Whether the option should show "Best offer" instead of standard discount copy
	 * @param {boolean} [option.selected] - Whether the option is selected by default
	 * @param {boolean} [option.isTrial] - Whether the option is a trial offer
	 * @param {boolean} [option.subscriptionAutoRenewTerm] - Whether the option is for an auto-renewing subscription
	 * @param {number} [option.trialAmount] - Amount used for trial pricing
	 * @param {string} [option.trialDuration] - Human-readable trial duration copy
	 * @param {string} [option.trialPrice] - Formatted trial price
	 * @param {string} [option.displayName] - Override label for the term title
	 * @param {string} [option.title] - Fallback title for legacy or non-period terms
	 * @param {string} [option.subTitle] - Optional subtitle shown alongside the term title
	 * @param {string} [option.chargeOnText] - Optional charge timing copy for non-period offers
	 * @param {boolean} [option.b2cPartnership] - Whether the option is part of a B2C partnership offer
	 * @param {string} [option.b2cDiscountCopy] - Partnership-specific discount copy
	 * @returns {React.ReactElement} A rendered payment term option
	 */
	const createPaymentTerm = (option) => {
		const className = classNames([
			'ncf__payment-term__item',
			'o-forms-input--radio-round',
			{ 'ncf__payment-term__item--discount': option.discount },
		]);
		const props = {
			type: 'radio',
			id: option.value,
			'data-base-amount': option.isTrial ? option.trialAmount : option.amount,
			name: inputName,
			value: option.value,
			className:
				'o-forms-input__radio o-forms-input__radio--right ncf__payment-term__input',
			...(option.selected && { defaultChecked: true }),
		};

		/**
		 * Determines whether input is a valid ISO 8601 duration value that can be decoded by the Period class.
		 * @returns {boolean}
		 */
		const isValidPeriod = () => {
			try {
				// Period should throw an error if it is not properly provided
				// in order to validate it, we just send in case type is string
				new Period(typeof option.value === 'string' ? option.value : '');
				return true;
			} catch (error) {
				return false;
			}
		};

		/**
		 * Compute monthly price for given term name.
		 * @returns {string}
		 */
		const getMonthlyPriceFromPeriod = () => {
			const periodObj = new Period(option.value);
			const monthlyPrice = periodObj.calculatePrice('P1M', option.amount);
			return new Monthly({
				value: monthlyPrice,
				symbol: option.symbol,
			}).getAmount('monthly');
		};

		/**
		 * Returns elements that include the text describing the price of the offer option.
		 * @returns {React.ReactElement}
		 */
		const getPriceText = () => {
			const isExpressedAsSinglePayment =
				!option.subscriptionAutoRenewTerm ||
				// With an auto-renewing annual term there is a high chance
				// it will not be the same price in the second year,
				// so we do not want to imply that the price will remain consistent.
				// For shorter auto-renewing terms there is higher confidence that the price
				// will remain consistent across subsequent terms.
				(option.subscriptionAutoRenewTerm && is52WeeksOrLonger(option.value));

			const isExpressedAsRecurringPayment = !isExpressedAsSinglePayment;

			if (isExpressedAsSinglePayment) {
				return (
					<React.Fragment>
						Single{' '}
						<span className="ncf__payment-term__price">{option.price}</span>{' '}
						payment
					</React.Fragment>
				);
			}

			if (isExpressedAsRecurringPayment) {
				return (
					<React.Fragment>
						<span className="ncf__payment-term__price">{option.price}</span> per{' '}
						{getDurationFromISO8601Value({
							iso8601Value: option.value,
						})}
					</React.Fragment>
				);
			}
		};

		/**
		 * Returns elements that include the text describing the price increase following the trial period.
		 * @returns {React.ReactElement}
		 */
		const getTrialPriceExplanatoryText = () => {
			return (
				<React.Fragment>
					Unless you cancel during your trial you will be billed{' '}
					<span className="ncf__payment-term__price">{option.price}</span> per{' '}
					{getDurationFromISO8601Value({
						iso8601Value: option.value,
					})}{' '}
					after the trial period.
				</React.Fragment>
			);
		};

		/**
		 * Returns elements that include the text describing how regularly an auto-reniewing subscription will renew.
		 * @returns {React.ReactElement}
		 */
		const getRenewalPeriodText = () => {
			return (
				<p className="ncf__payment-term__renews-text">
					Renews every{' '}
					{getDurationFromISO8601Value({ iso8601Value: option.value })} unless
					cancelled
				</p>
			);
		};

		/**
		 * Returns elements that include the text describing the monthly equivalent of the subscription.
		 * @returns {string}
		 */
		const getEquivalentMonthlyPrice = () => {
			if (Boolean(option.monthlyPrice) && option.monthlyPrice !== '0') {
				return isNaN(option.monthlyPrice)
					? option.monthlyPrice
					: `${option.symbol}${option.monthlyPrice}`;
			}

			return getMonthlyPriceFromPeriod();
		};

		/**
		 * Returns elements that include the text describing the monthly equivalent of the subscription.
		 * @returns {React.ReactElement}
		 */
		const getEquivalentMonthlyPriceText = () => {
			return (
				<span className="ncf__payment-term__equivalent-price">
					That’s equivalent to{' '}
					<span className="ncf__payment-term__monthly-price">
						{getEquivalentMonthlyPrice()}
					</span>{' '}
					per month
				</span>
			);
		};

		/**
		 * Creates the standard discount badge for an option.
		 * Displays either "Best offer" or "Save X off RRP" when a discount exists.
		 *
		 * @returns {React.ReactElement} The discount element, or false when no discount should be shown
		 */
		const createDiscount = () => {
			return (
				option.discount && (
					<span className="ncf__payment-term__discount">
						{option.bestOffer
							? 'Best offer'
							: `Save ${option.discount} off RRP`}
					</span>
				)
			);
		};

		/**
		 * Creates B2C partnership discount copy for eligible annual offers.
		 *
		 * @returns {React.ReactElement} The B2C discount element, or false when the option is not eligible
		 */
		const createB2cDiscountCopy = () => {
			return (
				option.name === 'annual' &&
				option.b2cPartnership &&
				option.b2cDiscountCopy && (
					<span className="ncf__payment-term__discount">
						{option.b2cDiscountCopy}
					</span>
				)
			);
		};

		/**
		 * Creates the description shown beneath the term title.
		 * This may include trial copy, price-per-period copy, equivalent monthly price,
		 * renewal messaging, or fallback non-period pricing text.
		 *
		 * @returns {React.ReactElement} The description block for the payment term
		 */
		const createDescription = () => {
			if (option.isTrial) {
				return (
					<div className="ncf__payment-term__description">
						{option.trialDuration || '4 weeks'} for{' '}
						<span className="ncf__payment-term__trial-price">
							{option.trialPrice}
						</span>
						<br />
						{getTrialPriceExplanatoryText()}
					</div>
				);
			}

			if (isValidPeriod(option.value)) {
				return (
					<div className="ncf__payment-term__description">
						{getPriceText()}

						{is90DaysOrLonger(option.value) && getEquivalentMonthlyPriceText()}

						{option.subscriptionAutoRenewTerm && getRenewalPeriodText()}
					</div>
				);
			}

			return (
				<div>
					<span className={largePrice ? 'ncf__payment-term__large-price' : ''}>
						{option.price}
					</span>
					{option.chargeOnText && (
						<p className="ncf__payment-term__charge-on-text">
							{option.chargeOnText}
						</p>
					)}
				</div>
			);
		};

		/**
		 * Builds the display name shown as the payment term title.
		 * May prepend trial copy, use the capitalised term name for auto-renewing terms,
		 * derive a human-readable period for valid non-renewing terms, or fall back to
		 * a legacy title when no valid period is available.
		 *
		 * @returns {string} The formatted display name for the payment term
		 */
		const getTermDisplayName = () => {
			const showTrialCopyInTitle =
				option.isTrial && !isPrintOrBundle && !isDigitalEdition;

			let termDisplayName = '';
			if (showTrialCopyInTitle) {
				const termName = option.displayName
					? option.displayName
					: 'Premium Digital';
				termDisplayName = `Trial: ${termName} - `;
			}

			const getTermPeriod = () => {
				if (option.subscriptionAutoRenewTerm && option.name) {
					return capitalise(option.name);
				}

				// custom offer with period provided
				if (!option.subscriptionAutoRenewTerm && isValidPeriod(option.value)) {
					return getDurationFromISO8601Value({
						iso8601Value: option.value,
						excludeAmountWhenSingular: false,
					});
				}
				// custom legacy cases, where period is not provided
				return option.title;
			};

			const termPeriod = getTermPeriod();
			termDisplayName = `${termDisplayName}${termPeriod} `;

			return termDisplayName;
		};

		return (
			<div key={option.value} className={className}>
				<input {...props} />
				<label
					htmlFor={option.value}
					className="o-forms-input__label ncf__payment-term__label"
				>
					{createDiscount()}
					{createB2cDiscountCopy()}

					<span
						className={classNames([
							'ncf__payment-term__title',
							{ 'ncf__payment-term__title--large-price': largePrice },
						])}
					>
						{getTermDisplayName()}
						{option.subTitle && (
							<span className="ncf__regular ncf__payment-term__sub-title">
								{option.subTitle}
							</span>
						)}
					</span>

					{createDescription()}
				</label>
			</div>
		);
	};

	return (
		<div
			id={fieldId}
			className="o-forms__group ncf__payment-term"
			data-country-code={billingCountry}
		>
			<div className={optionsInARow ? 'ncf__payment-term__options-grid' : ''}>
				{options.map((option) => createPaymentTerm(option))}
			</div>

			{showLegal && (
				<div className="ncf__payment-term__legal">
					{isAutoRenewingSubscriptionTermType && (
						<React.Fragment>
							<p>
								With all subscription types, we will automatically renew your
								subscription using the payment method provided unless you cancel
								before your renewal date.
							</p>
							<p className="o3-type-body-base">
								We will notify you at least 14 days in advance of any changes to
								the price in your subscription that would apply upon next
								renewal. Find out more about our cancellation policy in our{' '}
								<a
									href="https://help.ft.com/legal-privacy/terms-and-conditions/"
									title="FT Legal Terms and Conditions help page"
									target="_blank"
									rel="noopener noreferrer"
								>
									Terms &amp; Conditions
								</a>
								.
							</p>
						</React.Fragment>
					)}

					{isNonRenewingSubscriptionTermType && (
						<p className="o3-type-body-base">
							Find out more about our cancellation policy in our{' '}
							<a
								href="https://help.ft.com/legal-privacy/terms-and-conditions/"
								title="FT Legal Terms and Conditions help page"
								target="_blank"
								rel="noopener noreferrer"
							>
								Terms &amp; Conditions
							</a>
							.
						</p>
					)}
				</div>
			)}
		</div>
	);
}

PaymentTerm.propTypes = {
	fieldId: PropTypes.string,
	inputName: PropTypes.string,
	isPrintOrBundle: PropTypes.bool,
	isDigitalEdition: PropTypes.bool,
	options: PropTypes.arrayOf(
		PropTypes.shape({
			b2cDiscountCopy: PropTypes.string,
			isB2cPartnership: PropTypes.bool,
			discount: PropTypes.string,
			isTrial: PropTypes.bool,
			subscriptionAutoRenewTerm: PropTypes.bool,
			name: PropTypes.string.isRequired,
			price: PropTypes.string.isRequired,
			selected: PropTypes.bool,
			trialDuration: PropTypes.string,
			trialPrice: PropTypes.string,
			amount: PropTypes.string,
			symbol: PropTypes.string,
			trialAmount: PropTypes.number,
			value: PropTypes.string.isRequired,
			monthlyPrice: PropTypes.string,
			title: PropTypes.string,
			subTitle: PropTypes.string,
			bestOffer: PropTypes.bool,
			chargeOnText: PropTypes.string,
			fulfilmentOption: PropTypes.string,
		})
	),
	isAutoRenewingSubscriptionTermType: PropTypes.bool,
	isNonRenewingSubscriptionTermType: PropTypes.bool,
	showLegal: PropTypes.bool,
	largePrice: PropTypes.bool,
	optionsInARow: PropTypes.bool,
	billingCountry: PropTypes.string,
};
