const axios = require('axios');

const ProductsDBApi = require('../db/api/products');
const OrdersDBApi = require('../db/api/orders');
const SampleRequestsDBApi = require('../db/api/sample_requests');

function hasEnv(name) {
  return Boolean(process.env[name]);
}

function requiredEnv(name, provider) {
  const value = process.env[name];

  if (value) {
    return value;
  }

  const error = new Error(
    `${provider} is not configured. Missing environment variable: ${name}.`,
  );
  error.code = 400;
  throw error;
}

function providerStatus({
  key,
  name,
  category,
  description,
  env,
  actions,
  docsUrl,
}) {
  const missingEnv = env.filter((name) => !hasEnv(name));

  return {
    key,
    name,
    category,
    description,
    configured: missingEnv.length === 0,
    missingEnv,
    actions,
    docsUrl,
  };
}

function visibleExternalError(provider, error) {
  if (error.response) {
    const responseBody =
      typeof error.response.data === 'string'
        ? error.response.data
        : JSON.stringify(error.response.data);
    const visibleError = new Error(
      `${provider} request failed with status ${error.response.status}: ${responseBody}`,
    );
    visibleError.code = 400;
    throw visibleError;
  }

  throw error;
}

function moneyValue(value, fallback) {
  const numberValue = Number(value || fallback || 0);

  if (Number.isNaN(numberValue)) {
    return 0;
  }

  return numberValue;
}

function catalogRows(products) {
  const rows = products.map((product) => [
    product.sku || '',
    product.product_name || '',
    product.brand || '',
    product.pack_size || '',
    product.temperature_zone || '',
    product.product_status || '',
    product.base_price || '',
  ]);

  return [
    [
      'SKU',
      'Product Name',
      'Brand',
      'Pack Size',
      'Temperature Zone',
      'Status',
      'Base Price',
    ],
    ...rows,
  ];
}

function csvCell(value) {
  const stringValue = String(value || '');

  if (
    stringValue.includes(',') ||
    stringValue.includes('"') ||
    stringValue.includes('\n')
  ) {
    return `"${stringValue.replace(/"/g, '""')}"`;
  }

  return stringValue;
}

function rowsToCsv(rows) {
  return rows.map((row) => row.map(csvCell).join(',')).join('\n');
}

module.exports = class IntegrationsService {
  static async status() {
    return {
      headline: 'Foodservice integration center',
      strategy: 'Integrate with the restaurant stack; do not replace it.',
      providers: [
        providerStatus({
          key: 'csv',
          name: 'CSV price sheets',
          category: 'Catalog operations',
          description:
            'Import and export product catalogs and weekly price sheets.',
          env: [],
          actions: ['Export product catalog CSV', 'Import product catalog CSV'],
          docsUrl: '/api-docs',
        }),
        providerStatus({
          key: 'google_sheets',
          name: 'Google Sheets',
          category: 'Catalog operations',
          description:
            'Push the current catalog to a supplier-owned Google Sheet.',
          env: [
            'GOOGLE_SHEETS_ACCESS_TOKEN',
            'GOOGLE_SHEETS_SPREADSHEET_ID',
          ],
          actions: ['Append catalog rows to Google Sheets'],
          docsUrl:
            'https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append',
        }),
        providerStatus({
          key: 'stripe',
          name: 'Stripe',
          category: 'Payments',
          description:
            'Create sandbox checkout sessions for prepaid foodservice orders.',
          env: ['STRIPE_SECRET_KEY'],
          actions: ['Create checkout session'],
          docsUrl: 'https://docs.stripe.com/api/checkout/sessions',
        }),
        providerStatus({
          key: 'hubspot',
          name: 'HubSpot CRM',
          category: 'Sales ops',
          description:
            'Create CRM follow-up records from sample requests and buyer activity.',
          env: ['HUBSPOT_ACCESS_TOKEN'],
          actions: ['Push sample follow-up deal'],
          docsUrl:
            'https://developers.hubspot.com/docs/api-reference/latest/crm/using-object-apis',
        }),
        providerStatus({
          key: 'quickbooks',
          name: 'QuickBooks Online',
          category: 'Accounting',
          description:
            'Create invoices from approved purchase orders after fulfillment review.',
          env: [
            'QUICKBOOKS_ACCESS_TOKEN',
            'QUICKBOOKS_REALM_ID',
            'QUICKBOOKS_DEFAULT_CUSTOMER_ID',
            'QUICKBOOKS_DEFAULT_ITEM_ID',
          ],
          actions: ['Create invoice'],
          docsUrl:
            'https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/invoice',
        }),
        providerStatus({
          key: 'doordash',
          name: 'DoorDash Drive',
          category: 'Local delivery',
          description:
            'Quote local delivery for buyer purchase orders and sample shipments.',
          env: ['DOORDASH_ACCESS_TOKEN'],
          actions: ['Create delivery quote'],
          docsUrl: 'https://developer.doordash.com/en-US/api/drive/',
        }),
        providerStatus({
          key: 'shippo',
          name: 'Shippo',
          category: 'Carrier shipping',
          description:
            'Create carrier shipment requests and return available rates.',
          env: ['SHIPPO_API_TOKEN'],
          actions: ['Create shipment rates'],
          docsUrl: 'https://docs.goshippo.com/',
        }),
      ],
    };
  }

  static async exportProductsToGoogleSheets(currentUser) {
    const accessToken = requiredEnv(
      'GOOGLE_SHEETS_ACCESS_TOKEN',
      'Google Sheets',
    );
    const spreadsheetId = requiredEnv(
      'GOOGLE_SHEETS_SPREADSHEET_ID',
      'Google Sheets',
    );
    const range = process.env.GOOGLE_SHEETS_CATALOG_RANGE || 'Catalog!A1';

    const products = await ProductsDBApi.findAll(
      { page: 0, limit: 1000 },
      { currentUser },
    );

    try {
      const response = await axios.post(
        `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${encodeURIComponent(
          range,
        )}:append`,
        {
          majorDimension: 'ROWS',
          values: catalogRows(products.rows || []),
        },
        {
          params: { valueInputOption: 'USER_ENTERED' },
          headers: { Authorization: `Bearer ${accessToken}` },
        },
      );

      return {
        provider: 'google_sheets',
        rowsExported: (products.rows || []).length,
        updatedRange: response.data.updates?.updatedRange,
        spreadsheetId,
      };
    } catch (error) {
      visibleExternalError('Google Sheets', error);
    }
  }

  static async exportProductsCsv(currentUser) {
    const products = await ProductsDBApi.findAll(
      { page: 0, limit: 1000 },
      { currentUser },
    );

    return rowsToCsv(catalogRows(products.rows || []));
  }

  static async createStripeCheckoutSession(data) {
    const secretKey = requiredEnv('STRIPE_SECRET_KEY', 'Stripe');
    const amountCents = Number(data.amountCents || 12500);
    const currency = data.currency || 'usd';
    const orderLabel = data.orderLabel || 'Foodservice purchase order';
    const successUrl =
      data.successUrl ||
      process.env.STRIPE_SUCCESS_URL ||
      'https://example.com/integrations?stripe=success';
    const cancelUrl =
      data.cancelUrl ||
      process.env.STRIPE_CANCEL_URL ||
      'https://example.com/integrations?stripe=cancelled';

    const params = new URLSearchParams();
    params.append('mode', 'payment');
    params.append('success_url', successUrl);
    params.append('cancel_url', cancelUrl);
    params.append('line_items[0][quantity]', '1');
    params.append('line_items[0][price_data][currency]', currency);
    params.append('line_items[0][price_data][unit_amount]', String(amountCents));
    params.append('line_items[0][price_data][product_data][name]', orderLabel);

    try {
      const response = await axios.post(
        'https://api.stripe.com/v1/checkout/sessions',
        params,
        {
          headers: {
            Authorization: `Bearer ${secretKey}`,
            'Content-Type': 'application/x-www-form-urlencoded',
          },
        },
      );

      return {
        provider: 'stripe',
        sessionId: response.data.id,
        url: response.data.url,
        amountCents,
        currency,
      };
    } catch (error) {
      visibleExternalError('Stripe', error);
    }
  }

  static async syncSampleRequestToHubSpot(data) {
    const accessToken = requiredEnv('HUBSPOT_ACCESS_TOKEN', 'HubSpot');
    let sampleRequest = data.sampleRequest || null;

    if (data.sampleRequestId) {
      sampleRequest = await SampleRequestsDBApi.findBy({
        id: data.sampleRequestId,
      });
    }

    if (!sampleRequest) {
      const error = new Error(
        'HubSpot sync requires sampleRequestId or sampleRequest payload.',
      );
      error.code = 400;
      throw error;
    }

    const accountName =
      sampleRequest.account?.account_name || data.accountName || 'Buyer account';
    const productName =
      sampleRequest.product?.product_name || data.productName || 'Sample item';
    const sampleNumber =
      sampleRequest.sample_request_number || data.sampleNumber || 'Sample';

    try {
      const companyResponse = await axios.post(
        'https://api.hubapi.com/crm/v3/objects/companies',
        {
          properties: {
            name: accountName,
          },
        },
        {
          headers: { Authorization: `Bearer ${accessToken}` },
        },
      );

      const dealResponse = await axios.post(
        'https://api.hubapi.com/crm/v3/objects/deals',
        {
          properties: {
            dealname: `${sampleNumber} - ${productName}`,
            amount: data.amount || 0,
            description:
              sampleRequest.notes ||
              'Foodservice sample request created from the supplier portal.',
          },
        },
        {
          headers: { Authorization: `Bearer ${accessToken}` },
        },
      );

      return {
        provider: 'hubspot',
        companyId: companyResponse.data.id,
        dealId: dealResponse.data.id,
        sampleRequestNumber: sampleNumber,
      };
    } catch (error) {
      visibleExternalError('HubSpot', error);
    }
  }

  static async createQuickBooksInvoice(data) {
    const accessToken = requiredEnv('QUICKBOOKS_ACCESS_TOKEN', 'QuickBooks');
    const realmId = requiredEnv('QUICKBOOKS_REALM_ID', 'QuickBooks');
    const customerId =
      data.customerRef || requiredEnv('QUICKBOOKS_DEFAULT_CUSTOMER_ID', 'QuickBooks');
    const itemId =
      data.itemRef || requiredEnv('QUICKBOOKS_DEFAULT_ITEM_ID', 'QuickBooks');
    let order = data.order || null;

    if (data.orderId) {
      order = await OrdersDBApi.findBy({ id: data.orderId });
    }

    const amount = moneyValue(order?.order_total, data.amount || 125);
    const description =
      order?.order_number || data.description || 'Foodservice purchase order';

    try {
      const response = await axios.post(
        `https://quickbooks.api.intuit.com/v3/company/${realmId}/invoice`,
        {
          CustomerRef: { value: customerId },
          Line: [
            {
              Amount: amount,
              Description: description,
              DetailType: 'SalesItemLineDetail',
              SalesItemLineDetail: {
                ItemRef: { value: itemId },
              },
            },
          ],
        },
        {
          params: { minorversion: 75 },
          headers: {
            Authorization: `Bearer ${accessToken}`,
            Accept: 'application/json',
            'Content-Type': 'application/json',
          },
        },
      );

      return {
        provider: 'quickbooks',
        invoiceId: response.data.Invoice?.Id,
        docNumber: response.data.Invoice?.DocNumber,
        amount,
      };
    } catch (error) {
      visibleExternalError('QuickBooks', error);
    }
  }

  static async createDoorDashQuote(data) {
    const accessToken = requiredEnv('DOORDASH_ACCESS_TOKEN', 'DoorDash Drive');
    const quotePayload = {
      external_delivery_id: data.externalDeliveryId || `sample-${Date.now()}`,
      pickup_address:
        data.pickupAddress ||
        process.env.DOORDASH_PICKUP_ADDRESS ||
        '200 N Green St, Chicago, IL 60607',
      pickup_business_name:
        data.pickupBusinessName ||
        process.env.DOORDASH_PICKUP_BUSINESS_NAME ||
        'Northstar Foodservice',
      pickup_phone_number:
        data.pickupPhoneNumber ||
        process.env.DOORDASH_PICKUP_PHONE ||
        '+13125550101',
      dropoff_address:
        data.dropoffAddress ||
        '500 W Madison St, Chicago, IL 60661',
      dropoff_business_name: data.dropoffBusinessName || 'Buyer kitchen',
      dropoff_phone_number: data.dropoffPhoneNumber || '+13125550102',
      order_value: Number(data.orderValue || 12500),
    };

    try {
      const response = await axios.post(
        'https://openapi.doordash.com/drive/v2/quotes',
        quotePayload,
        {
          headers: {
            Authorization: `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
          },
        },
      );

      return {
        provider: 'doordash',
        quote: response.data,
      };
    } catch (error) {
      visibleExternalError('DoorDash Drive', error);
    }
  }

  static async createShippoShipment(data) {
    const apiToken = requiredEnv('SHIPPO_API_TOKEN', 'Shippo');
    const shipmentPayload = {
      address_from: data.addressFrom || {
        name: process.env.SHIPPO_FROM_NAME || 'Northstar Foodservice',
        street1: process.env.SHIPPO_FROM_STREET1 || '200 N Green St',
        city: process.env.SHIPPO_FROM_CITY || 'Chicago',
        state: process.env.SHIPPO_FROM_STATE || 'IL',
        zip: process.env.SHIPPO_FROM_ZIP || '60607',
        country: process.env.SHIPPO_FROM_COUNTRY || 'US',
        phone: process.env.SHIPPO_FROM_PHONE || '+13125550101',
        email: process.env.SHIPPO_FROM_EMAIL || 'ops@example.com',
      },
      address_to: data.addressTo || {
        name: 'Buyer receiving',
        street1: '500 W Madison St',
        city: 'Chicago',
        state: 'IL',
        zip: '60661',
        country: 'US',
        phone: '+13125550102',
        email: 'buyer@example.com',
      },
      parcels: data.parcels || [
        {
          length: '12',
          width: '10',
          height: '8',
          distance_unit: 'in',
          weight: '6',
          mass_unit: 'lb',
        },
      ],
      async: false,
    };

    try {
      const response = await axios.post(
        'https://api.goshippo.com/shipments/',
        shipmentPayload,
        {
          headers: {
            Authorization: `ShippoToken ${apiToken}`,
            'Content-Type': 'application/json',
          },
        },
      );

      return {
        provider: 'shippo',
        objectId: response.data.object_id,
        rates: response.data.rates || [],
      };
    } catch (error) {
      visibleExternalError('Shippo', error);
    }
  }

  static async createDeliveryWorkflow(data) {
    if (data.provider === 'shippo') {
      return IntegrationsService.createShippoShipment(data);
    }

    return IntegrationsService.createDoorDashQuote(data);
  }
};
