import { Auth as VendorAuth } from '@blocklet/payment-vendor';

import { joinURL, withQuery } from 'ufo';
import { ProductVendor } from '../../../store/models';
import dayjs from '../../dayjs';
import logger from '../../logger';
import { formatToShortUrl } from '../../url';
import { api } from '../../util';
import {
  CheckOrderStatusParams,
  CheckOrderStatusResult,
  FulfillOrderParams,
  FulfillOrderResult,
  RefundRequestParams,
  RefundRequestResult,
  ReturnRequestParams,
  ReturnRequestResult,
  VendorAdapter,
  VendorConfig,
} from './types';
import { formatVendorUrl } from './util';

async function getBlockletInfo(appUrl: string): Promise<any> {
  const url = joinURL(appUrl, '__blocklet__.js?type=json&noCache=1&owner=1');
  try {
    const blocklet = await api.get(url, { headers: { 'Content-Type': 'application/json' } });
    return blocklet.data;
  } catch (error: any) {
    logger.error('Failed to get blocklet info', { url, error });
    return null;
  }
}

const doRequestVendorData = async (
  vendor: ProductVendor,
  orderId: string,
  baseUrl: string,
  options: { shortUrl: boolean; query?: Record<string, string>; type?: 'order' | 'status' }
) => {
  const name = vendor?.name;
  const key = vendor?.vendor_key;
  const { shortUrl, query, type } = options;
  const { headers } = await VendorAuth.signRequestWithHeaders({ ...query });

  let url = baseUrl;
  if (query) {
    url = withQuery(baseUrl, query);
  }

  return fetch(url, { headers, method: 'GET' })
    .then(async (r) => {
      const data = await r.json();
      if (r.status !== 200) {
        logger.error(`vendor ${type} fetch failed`, {
          status: r.status,
          vendorId: vendor.id,
          vendorKey: vendor.vendor_key,
          orderId,
          url,
          query,
          data,
        });
        throw new Error(
          `vendor status fetch failed, vendor: ${vendor.vendor_key}, orderId: ${orderId}, status: ${r.status}, url: ${url}`
        );
      } else if (type === 'order') {
        logger.info('vendor order fetch success', {
          vendorId: vendor.id,
          vendorKey: vendor.vendor_key,
          orderId,
          url,
          query,
        });
      }

      if (!shortUrl || !data.dashboardUrl) {
        return {
          ...data,
          name,
          key,
        };
      }

      const validUntil = dayjs().add(20, 'minutes').format('YYYY-MM-DDTHH:mm:ss+00:00');
      const maxVisits = 5;
      const homeUrl = await formatToShortUrl({ url: data.homeUrl, maxVisits, validUntil });
      const dashboardUrl = await formatToShortUrl({ url: data.dashboardUrl, maxVisits, validUntil });
      return {
        ...data,
        name,
        key,
        homeUrl,
        dashboardUrl,
      };
    })
    .catch((e) => ({ error: e.message }));
};

export class LauncherAdapter implements VendorAdapter {
  private vendorConfig: VendorConfig | null = null;
  private vendorKey: string;

  constructor(vendorInfo: VendorConfig) {
    this.vendorKey = vendorInfo.vendor_key;
    this.vendorConfig = vendorInfo;
  }

  async getVendorConfig(): Promise<VendorConfig> {
    if (this.vendorConfig === null) {
      this.vendorConfig = await ProductVendor.findOne({ where: { vendor_key: this.vendorKey } });
      if (!this.vendorConfig) {
        throw new Error(`Vendor not found: ${this.vendorKey}`);
      }
    }
    return this.vendorConfig;
  }

  async fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult> {
    logger.info('Creating launcher order via real API', {
      checkoutSessionId: params.checkoutSessionId,
      productCode: params.productCode,
      customerId: params.customerId,
      description: params.description,
      bindCapInfo: params.deliveryParams?.customParams?.bindDomainCap ? 'present' : 'not present',
    });

    const vendorConfig = await this.getVendorConfig();

    try {
      const orderData = {
        checkoutSessionId: params.checkoutSessionId,
        description: params.description,
        userInfo: params.userInfo,
        deliveryParams: params.deliveryParams,
      };

      // Extract bindDomainCap related params if present
      const customParams = params.deliveryParams?.customParams || {};
      const { domain, sessionId, bindDomainCap, domainNftDid } = customParams;

      if (bindDomainCap && domain && sessionId) {
        logger.info('passing domain binding authorization to Blocklet Server', {
          domain,
          sessionId: `${sessionId.substring(0, 8)}...`, // Log partial sessionId for security
          hasBindCap: true,
        });

        // Ensure bindDomainCap data is passed through to Blocklet Server
        orderData.deliveryParams = {
          ...orderData.deliveryParams,
          customParams: {
            ...orderData.deliveryParams.customParams,
            domain,
            sessionId,
            bindDomainCap,
            domainNftDid,
          },
        };
      }

      const { headers, body } = await VendorAuth.signRequestWithHeaders(orderData);
      const url = formatVendorUrl(vendorConfig, '/api/vendor/deliveries');
      const response = await fetch(url, {
        method: 'POST',
        headers,
        body,
      });

      if (!response.ok) {
        const errorBody = await response.text();
        logger.error('call launcher API error', {
          url,
          status: response.status,
          statusText: response.statusText,
          body: errorBody,
        });
        throw new Error(`Launcher API error: ${response.status} ${response.statusText}`);
      }

      const launcherResult = await response.json();

      const result: FulfillOrderResult = {
        orderId: launcherResult.orderId || launcherResult.vendorOrderId || `launcher_${Date.now()}`,
        status: launcherResult.status || 'pending',
        serviceUrl: launcherResult.serviceUrl || launcherResult.appUrl,
        estimatedTime: launcherResult.estimatedTime || 300,
        message: launcherResult.message || 'Launcher order created successfully',
        vendorOrderId: launcherResult.vendorOrderId || launcherResult.orderId,
        progress: launcherResult.progress || 0,
        orderDetails: {
          productCode: params.productCode,
          customerId: params.customerId,
          amount: params.amount,
          currency: params.currency,
          quantity: params.quantity,
          invoiceId: params.invoiceId,
          customParams: {
            ...params.customParams,
            // Include domain binding info if available
            ...(customParams.bindDomainCap
              ? {
                  domain: customParams.domain,
                  sessionId: customParams.sessionId,
                  bindDomainCap: customParams.bindDomainCap,
                  domainNftDid: customParams.domainNftDid,
                }
              : {}),
          },
        },
        installationInfo: {
          appId: launcherResult.appId,
          appUrl: launcherResult.appUrl,
          adminUrl: launcherResult.adminUrl,
          status: launcherResult.installationStatus || launcherResult.status,
          estimatedCompletionTime: launcherResult.estimatedCompletionTime,
        },
      };

      logger.info('Launcher order created successfully', {
        orderId: result.orderId,
        status: result.status,
        serviceUrl: result.serviceUrl,
      });

      return result;
    } catch (error: any) {
      logger.error('Failed to create launcher order', {
        error,
        productCode: params.productCode,
        customerId: params.customerId,
      });

      throw error;
    }
  }

  async requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult> {
    logger.info('Requesting return for Launcher order', {
      orderId: params.orderId,
      reason: params.reason,
    });

    const vendorConfig = await this.getVendorConfig();
    const { headers, body } = await VendorAuth.signRequestWithHeaders(params);

    const response = await fetch(formatVendorUrl(vendorConfig, '/api/vendor/return'), {
      method: 'POST',
      headers,
      body,
    });

    if (!response.ok) {
      const errorBody = await response.text();
      logger.error('Launcher API error', {
        status: response.status,
        statusText: response.statusText,
        body: errorBody,
      });
      throw new Error(`Launcher API error: ${response.status} ${response.statusText}`);
    }

    const launcherResult = await response.json();

    return launcherResult;
  }

  async requestRefund(params: RefundRequestParams): Promise<RefundRequestResult> {
    logger.info('Requesting refund for payout', { params });

    const vendorConfig = await this.getVendorConfig();
    const { headers, body } = await VendorAuth.signRequestWithHeaders(params);

    const response = await fetch(formatVendorUrl(vendorConfig, '/api/vendor/refund'), {
      method: 'POST',
      headers,
      body,
    });

    if (!response.ok) {
      const errorBody = await response.text();
      logger.error('Vendor refund API error', {
        status: response.status,
        statusText: response.statusText,
        body: errorBody,
        orderId: params.orderId,
      });
      throw new Error(`Vendor refund API error: ${response.status} ${response.statusText}`);
    }

    const result = await response.json();

    logger.info('Vendor refund request completed', {
      orderId: params.orderId,
      status: result.status,
    });

    return result;
  }

  async checkOrderStatus(params: CheckOrderStatusParams): Promise<CheckOrderStatusResult> {
    try {
      const blockletInfo = await getBlockletInfo(params.appUrl);

      let status: 'processing' | 'completed' | 'failed' = 'processing';
      if (blockletInfo.status === 'running') {
        if (!!blockletInfo.ownerDid && blockletInfo.componentMountPoints.every((x: any) => x.status === 'running')) {
          status = 'completed';
        } else if (blockletInfo.componentMountPoints.some((x: any) => ['failed', 'error'].includes(x.status))) {
          status = 'failed';
        }
      } else if (['failed', 'error'].includes(blockletInfo.status)) {
        status = 'failed';
      }
      return { status };
    } catch (error: any) {
      logger.error('Failed to check order status', { error, appUrl: params.appUrl });
      throw error;
    }
  }

  getOrderStatus(vendor: ProductVendor, orderId: string, options: { shortUrl: boolean }): Promise<any> {
    const url = formatVendorUrl(vendor, `/api/vendor/status/${orderId}`);

    return doRequestVendorData(vendor, orderId, url, options);
  }

  async getOrder(
    vendor: ProductVendor,
    orderId: string,
    options: { shortUrl: boolean; appUrl?: string; ownerDid?: string; appDid?: string }
  ): Promise<any> {
    const { appUrl, ownerDid: ownerDidParam, appDid: appDidParam } = options;
    let ownerDid = ownerDidParam || '';
    let appDid = appDidParam || '';
    if (appUrl) {
      try {
        const blockletInfo = await getBlockletInfo(appUrl);
        ownerDid = blockletInfo.ownerDid;
        appDid = blockletInfo.appId;
      } catch (error: any) {
        logger.error('Failed to get blocklet info', { appUrl, error });
      }
    }

    const url = formatVendorUrl(vendor, `/api/vendor/orders/${orderId}`);

    return doRequestVendorData(vendor, orderId, url, { ...options, query: { ownerDid, appDid }, type: 'order' });
  }

  async connectTest(): Promise<any> {
    const url = formatVendorUrl(this.vendorConfig!, '/api/vendor/health');
    const { headers } = await VendorAuth.signRequestWithHeaders({});
    return fetch(url, { headers }).then((res) => res.json());
  }
}
