import React, {useState, useRef, useContext, useEffect} from 'react';

import {DonationPrices} from './donation_prices';
import {isNotBlank} from './validators';
import {isValidEmail} from './validators';
import {findErrorByName} from './named_error';
import {NamedError} from './named_error';
import {PaymentOptionButton} from './payment_option_button';
import {PerkSelectionSection} from './perk_selection_section';
import {GivingInfoForm} from './giving_info_form';
import {GivingErrorContainer} from './giving_error_container';
import {getPaymentMethods, sweatshirtSizes, requiredFields, textFields, displayPerkSelections, stripeTokenFieldMap, restrictedCountries} from './settings';
import {AppContext} from './app_context';
import {LoadingDialogReactPages} from './loading_dialog_react_pages';
import {FrequencySelector} from './frequency_selector';
import {determineBaseUrl} from './base_url';

export function GivingForm(props) {
  const {frequencyOptions, pricesOnButtons, donateProccessorFallbackUrl, perks, initialSelectedPrices, defaultFrequencyForBoth, successRedirectUrl, shirtFits, showGifts, paymentMethods, stripePublishableKey} = props;
  const [frequency, setFrequency] = useState((frequencyOptions == 'both')? defaultFrequencyForBoth : frequencyOptions);
  const displayPerkSelection = displayPerkSelections[frequency];
  const [noPerk, setNoPerk] = useState(true);
  const [selectedPrice, setSelectedPrice] = useState(initialSelectedPrices[frequency]);
  const [selectedPerk, setSelectedPerk] = useState(null);
  const [perkOption, setPerkOption] = useState('strength-in-numbers');
  const [paymentMethod, setPaymentMethod] = useState(paymentMethods.includes('credit-card')? 'credit-card' : 'paypal');
  const [mailingListOptIn, setMailingListOptIn] = useState(false);
  const [loading, setLoading] = useState(false);
  const [errors, setErrors] = useState([]);
  const [formState, setFormState] = useState('waiting');
  const [stripeErrors, setStripeErrors] = useState({
    'cardNumber': new NamedError('cardNumber', "Your card number is incomplete."),
    'cardExpiry': new NamedError('cardExpiry', "Your card's expiration date is incomplete."),
    'cardCvc': new NamedError('cardCvc', "Your card's security code is incomplete."),
  });
  const [fitsAndSizes, setFitsAndSizes] = useState({
    'shirt1Fit': null,
    'shirt2Fit': null,
    'shirt1Size': null,
    'shirt2Size': null,
    'sweatshirtSize': null
  });
  const [formData, setFormData] = useState({
    'firstName': null,
    'lastName': null,
    'email': null,
    'country': 'US',
    'region': null,
  });

  const appContext = useContext(AppContext);

  const donateProccessorBaseUrl = determineBaseUrl(donateProccessorFallbackUrl);

  let normalizedSuccessRedirectUrl = successRedirectUrl.trim();
  if (normalizedSuccessRedirectUrl == '') {
    normalizedSuccessRedirectUrl = 'donate-thank-you';
  }
  normalizedSuccessRedirectUrl = appContext.assetBaseUrl.path + normalizedSuccessRedirectUrl;

  const priceOtherRef = useRef(null);

  const requiredFieldsForPerkAndPayment = () => {
    return requiredFields(paymentMethod, selectedPerk);
  }

  const requiredFitAndSizeFields = () => {
    const perk = findPerkByName(selectedPerk);
    if (perk != undefined) {
      return perk.requiredFields;
    }
    return [];
  }

  useEffect(() => {
    const error = findErrorByName(errors, 'donationLessThanTwo');
    if (error != undefined) {
      priceOtherRef.current.focus();
    }
  });

  const addError = (error) => {
    setErrors([...errors, error]);
  }

  const findPerkByName = (name) => {
    return perks.find((perk) => perk.name == name);
  }

  const onPriceChange = (price) => {
    setSelectedPrice(price);
    if (getDefaultPerk(price) == null || noPerk) {
      setNoPerk(true);
      setSelectedPerk(null);
    } else {
      setSelectedPerk(getDefaultPerk(price));
    }
  }

  const getDefaultPerk = (price) => {
    let defaultPerk = null;
    for (const perk of perks) {
      if (price >= perk.price[frequency]) {
        defaultPerk = perk.name;
      }
    }
    return defaultPerk;
  }

  const getPerkPrice = (perkName) => {
    for (const perk of perks) {
      if (perk.name == perkName) {
        return perk.price[frequency];
      }
    }
  }

  const getSelectedPerkOptionProperties = (selectedPerkOption) => {
    for (const perk of perks) {
      if (perk.options != null) {
        for (const option of perk.options) {
          if (option.name == selectedPerkOption) {
            return option;
          }
        }
      }
    }
  }

  const getPerkFriendlyName = (perkName) => {
    for (const perk of perks) {
      if (perk.name == perkName) {
        return perk.friendly_name[frequency];
      }
    }
    return '';
  }

  const onNoPerkCheckboxChange = (event) => {
    if (noPerk) {
      setSelectedPerk(getDefaultPerk(selectedPrice));
    } else {
      setSelectedPerk(null);
    }
    setNoPerk(event.target.checked);
  };

  const getPaymentOptionButtons = () => {
    const paymentMethodsProperties = getPaymentMethods(appContext);
    return (paymentMethodsProperties.filter(method =>
      paymentMethods.includes(method.name)
    ).map(method =>
      <PaymentOptionButton
        key={method.name}
        method={method}
        paymentMethod={paymentMethod}
        onPaymentSelection={onPaymentSelection}
      />
    ));
  }

  const prepareFieldsData = () => {
    let fields = {};
    for (const key in formData) {
      const value = formData[key];
      if (value) {
        fields[key] = value;
      }
    }
    if (noPerk) {
      fields['no-perk-checkbox'] = noPerk;
    }
    if (selectedPerk == 't-shirt-pack'){
      fields['Fit'] = fitsAndSizes['shirt2Fit'];
      fields['Size'] = fitsAndSizes['shirt2Size'];
    } else {
      fields['Fit'] = '--none--';
      fields['Size'] = '--none--';
    }
    if (selectedPerk == 'sweatshirt') {
      let original_comments = fields['comments'];
      let original_size = fitsAndSizes['sweatshirtSize'];
      fields['comments'] = `SWEATSHIRTSIZE:${original_size}\n${original_comments}`;
    }
    return fields;
  };

  const preparePerkData = () => {
    let perkData = {'name': 'none'};
    if (selectedPerk) {
      perkData['name'] = selectedPerk;
      if (selectedPerk == 't-shirt') {
        perkData['size-1'] = fitsAndSizes['shirt1Size'];
        perkData['fit-1'] = fitsAndSizes['shirt1Fit'];
        perkData['style-1'] = 'take-back-internet';
      } else if (selectedPerk == 't-shirt-pack'){
        perkData['style-1'] = perkOption;
        perkData['fit-1'] = 'slim';
        perkData['fit-2'] = fitsAndSizes['shirt1Fit'];
        perkData['size-1'] = 's';
        perkData['size-2'] = fitsAndSizes['shirt1Size'];
      } else if (selectedPerk == 'sweatshirt') {
        // This is hardcoded as a small due to a bug in civi
        // https://gitlab.torproject.org/tpo/web/civicrm/-/issues/39#note_2774374
        // the actual size is prepended to the comment field in prepareFieldsData
        //perkData['size-1'] = fitsAndSizes['sweatshirtSize'];
        perkData['size-1'] = 's';
      }
    }
    return perkData;
  };

  const validateField = (fieldData, fieldName, validator) => {
    if (!(fieldName in fieldData)) {
      return false;
    }
    return validator(fieldData[fieldName]);
  }

  const getFieldPlaceholder = (fieldName) => {
    if (fieldName in textFields) {
      return textFields[fieldName]['placeholder'];
    }
    return null;
  }

  const validateRequiredFieldsAndDonationAmount = () => {
    const newErrors = [];
    const requiredFields = requiredFieldsForPerkAndPayment();
    for (const fieldName of requiredFields) {
      if (!validateField(formData, fieldName, isNotBlank)) {
        let placeholder = getFieldPlaceholder(fieldName);
        if (placeholder == null) {
          placeholder = fieldName;
        }
        const errorMessage = placeholder + ' must be filled out';
        newErrors.push(new NamedError(fieldName, errorMessage));
      }
    }
    if (!validateField(formData, 'email', isValidEmail)) {
      newErrors.push(new NamedError('email', 'Invalid email'));
    }
    if (selectedPrice < 200) {
      newErrors.push(new NamedError('donationLessThanTwo', '$2 minimum donation'));
    }
    const requiredFitAndSizeFieldNames = requiredFitAndSizeFields();
    for (const fieldName of requiredFitAndSizeFieldNames) {
      if (fieldName != 'shirt2Fit' && fieldName != 'shirt2Size' && !validateField(fitsAndSizes, fieldName, isNotBlank)) {
    console.log('HERE');
          console.log(fieldName);
        newErrors.push(new NamedError(fieldName, fieldName + ' must be filled out'));
      }
    }
    if (paymentMethod == 'credit-card') {
      for (const stripeField in stripeErrors) {
        newErrors.push(stripeErrors[stripeField]);
      }
    }
    return newErrors;
  }

  const createStripeTokenData = () => {
    const tokenData = {};
    for (const fieldName in stripeTokenFieldMap) {
      const stripeName = stripeTokenFieldMap[fieldName];
      if (fieldName in formData) {
        tokenData[stripeName] = formData[fieldName];
      }
    }
    return tokenData;
  }

  const onSubmit = async (e) => {
    if (paymentMethod == 'credit-card') {
      e.preventDefault();
      const newErrors = validateRequiredFieldsAndDonationAmount();
      if (newErrors.length == 0) {
        setFormState('submitted');
        setLoading(true);
      } else {
        setErrors(newErrors);
      }
    }
  }

  const createBillingAgreement = async (data, actions) => {
    const newErrors = validateRequiredFieldsAndDonationAmount();
    if (newErrors.length > 0) {
      setErrors(newErrors);
      throw new Error("validation errors");
    }
    setErrors([]);
    let recurring = false;
    if (frequency == 'monthly') {
      recurring = true;
    }
    const options = {
      credentials: 'include',
      headers: {
        'Accept': 'application/json, text/html',
        'Content-Type': 'application/json',
      },
      method: 'POST',
      body: JSON.stringify({'amount': selectedPrice, 'recurring': recurring}),
    };
    const response = await fetch(`${donateProccessorBaseUrl}/setExpressCheckout`, options);
    const response_data = await response.json();
    if ('token' in response_data) {
      return response_data['token'];
    } else if ('errors' in response_data) {
      toggleLoading(false);
      const errorMessage = response_data['errors'].join("\n");
      newErrors.push(new NamedError('paypalError', errorMessage));
      setErrors(newErrors);
      throw new Error(errorMessage);
    } else {
      const body = await response.text();
      const errorMessage = "Invalid response from /setExpressCheckout: " + response.status + ": " + body;
      newErrors.push(new NamedError('paypalError', errorMessage));
      setErrors(newErrors);
      throw new Error(errorMessage);
    }
  };

  const onPerkSelection = (event, perk) => {
    const newPerk = event.target.getAttribute('name');
    if (selectedPrice >= perk.price[frequency]) {
      setNoPerk(false);
      setSelectedPerk(newPerk);
    }
  };

  const onMailingListOptInCheckboxChange = (event) => {
    setMailingListOptIn(event.target.checked);
  };

  const countryChanged = (event) => {
    if (restrictedCountries.includes(event.target.value.toUpperCase())) {
      addError(new NamedError(
          'country',
          'Due to shipping restrictions, we currently can not ship to Ukraine, or Russia. We apologize for the inconvenience.'
      ));
    }
    setFormData({...formData, country: event.target.value});
  };

  const regionChanged = (event) => {
    setFormData({...formData, region: event.target.value});
  };

  const onInputFieldChange = (event) => {
    const fieldName = event.target.getAttribute('name');
    const newState = {...formData};
    newState[fieldName] = event.target.value;
    setFormData(newState);
  };

  const updateFitsAndSizes = (shirt, fitOrSize, newValue) => {
    if (shirt == 'sweatshirt') {
      setFitsAndSizes({...fitsAndSizes, sweatshirtSize: newValue});
    } else {
      const typeName = fitOrSize.charAt(0).toUpperCase() + fitOrSize.slice(1);
      const varName = `${shirt}${typeName}`;
      const newState = {...fitsAndSizes};
      newState[varName] = newValue;
      setFitsAndSizes(newState);
    }
  }

  const onPaymentSelection = (event) => {
    setPaymentMethod(event.target.getAttribute('name'));
    setErrors([]);
  }

  const onStripeFieldChange = (event) => {
    const field = event.elementType;
    const newState = {...stripeErrors};
    if (event.error != undefined) {
      const newError = new NamedError(field, event.error.message);
      newState[field] = newError;
      setStripeErrors(newState);
    } else {
      delete newState[field];
      setStripeErrors(newState);
    }
  }

  const onFrequencySelection = (newFrequency) => {
    setFrequency(newFrequency);
    setSelectedPrice(initialSelectedPrices[newFrequency]);
  };

  const paymentMethodSelectionRegion = () => {
    if (paymentMethods.length > 1) {
      return (
        <div className="payment-method-area">
          <h4 className="payment-method-question">
            how do you want to <span className="lime">DONATE</span>?
          </h4>
          <div className="payment-option">
            {getPaymentOptionButtons()}
          </div>
        </div>
      );
    }
    return null;
  }

  let perkSelectionSection = null;
  if (showGifts == 'True') {
    perkSelectionSection = (
      <PerkSelectionSection
        displayPerkSelection={displayPerkSelection}
        noPerk={noPerk}
        onNoPerkCheckboxChange={onNoPerkCheckboxChange}
        perks={perks}
        onPerkSelection={onPerkSelection}
        selectedPrice={selectedPrice}
        selectedPerk={selectedPerk}
        setPerkOption={setPerkOption}
        perkOption={perkOption}
        frequency={frequency}
      />
    );
  }

  return(
    <React.Fragment>
      <FrequencySelector
        frequencyOptions={frequencyOptions}
        frequency={frequency}
        onFrequencySelection={onFrequencySelection}
      />
      <form action={window.reactComponents[0].props.donateProccessorFallbackUrl + "/donate"} method="POST" id="donationForm" onSubmit={onSubmit}>
        <div className="donation-selection-area">
          <DonationPrices
            onPriceChange={onPriceChange}
            pricesOnButtons={pricesOnButtons[frequency]}
            selectedPrice={selectedPrice}
            priceOtherUseRef={priceOtherRef}
          />
          {perkSelectionSection}
          <div className="totals-area"></div>
        </div>
        <div className="payment-method-area">
          <h4 className="payment-method-question">
            how do you want to <span className="donate-main-color">DONATE</span>?
          </h4>
          <div className="payment-option">
            {getPaymentOptionButtons()}
          </div>
        </div>
        <div className="info-area">
          <GivingInfoForm
            paymentMethod={paymentMethod}
            mailingListOptIn={mailingListOptIn} onMailingListOptInCheckboxChange={onMailingListOptInCheckboxChange}
            selectedPerk={selectedPerk}
            perkOption={perkOption}
            perkOptionProperties={getSelectedPerkOptionProperties(perkOption)}
            shirtFits={shirtFits}
            sweatshirtSizes={sweatshirtSizes}
            frequency={frequency}
            selectedPrice={selectedPrice}
            noPerk={noPerk}
            selectedPerkFriendlyName={getPerkFriendlyName(selectedPerk)}
            requiredFields={requiredFieldsForPerkAndPayment()}
            errors={errors}
            textFields={textFields}
            stripeSubmitHandle={onSubmit}
            formData={formData}
            countryChanged={countryChanged}
            regionChanged={regionChanged}
            onInputFieldChange={onInputFieldChange}
            fitsAndSizes={fitsAndSizes}
            updateFitsAndSizes={updateFitsAndSizes}
            validateRequiredFieldsAndDonationAmount={validateRequiredFieldsAndDonationAmount}
            preparePerkData={preparePerkData}
            prepareFieldsData={prepareFieldsData}
            createBillingAgreement={createBillingAgreement}
            onStripeFieldChange={onStripeFieldChange}
            addError={addError}
            displayPerkSelection={displayPerkSelection}
            donateProccessorBaseUrl={donateProccessorBaseUrl}
            successRedirectUrl={normalizedSuccessRedirectUrl}
            setLoading={setLoading}
            stripePublishableKey={stripePublishableKey}
            createStripeTokenData={createStripeTokenData}
            paymentMethods={paymentMethods}
            formState={formState}
            setErrors={setErrors}
            restrictedCountries={restrictedCountries}
          />
        </div>
        <GivingErrorContainer errors={errors} />
      </form>
      <LoadingDialogReactPages open={loading} />
    </React.Fragment>
  );
}
