import { VendorAuth } from '@blocklet/payment-vendor';
import { toBase58 } from '@ocap/util';
import stableStringify from 'json-stable-stringify';
import { v4 as uuidV4 } from 'uuid';

import { ProductVendor } from '../../../store/models';
import { wallet } from '../../auth';
import logger from '../../logger';
import {
  CheckOrderStatusParams,
  CheckOrderStatusResult,
  FulfillOrderParams,
  FulfillOrderResult,
  RefundRequestParams,
  RefundRequestResult,
  ReturnRequestParams,
  ReturnRequestResult,
  VendorAdapter,
  VendorConfig,
} from './types';
import { formatVendorUrl } from './util';

const DOMAIN_CONFLICT_ERROR_CODE = 'ERROR_DOMAIN_NOT_AVAILABLE';

export const parseDomainLength = (len: number | string | undefined, defaultValue: number) => {
  try {
    if (len === undefined) {
      return defaultValue;
    }

    if (typeof len === 'number') {
      return len;
    }

    return parseInt(len, 10) || defaultValue;
  } catch (error) {
    logger.error('failed to parse domain length', {
      error,
      len,
      defaultValue,
    });

    return defaultValue;
  }
};

export const generateRandomSubdomain = (totalLength: number = 8): string => {
  // Generate timestamp-based string for uniqueness
  const timestamp = Date.now();
  const timeStr = timestamp.toString(36);
  let result = timeStr.slice(-Math.min(timeStr.length, totalLength));

  // Pad with random chars if needed
  while (result.length < totalLength) {
    result = Math.floor(Math.random() * 36).toString(36) + result;
  }

  return result.slice(0, totalLength);
};

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

  constructor(vendorInfo: VendorConfig | string) {
    if (typeof vendorInfo === 'string') {
      this.vendorKey = vendorInfo;
      this.vendorConfig = null;
    } else {
      this.vendorKey = vendorInfo.id;
      this.vendorConfig = vendorInfo;
    }
  }

  /**
   * Submit domain order with retry logic for domain conflicts
   */
  private async submitDomainOrderWithRetry(
    orderData: any,
    url: string,
    rootDomain: string,
    totalLength: number,
    maxRetries: number = 5
  ): Promise<{ response: Response; subdomain: string; domain: string; bindDomainCap: any }> {
    let lastError: Error | null = null;

    // eslint-disable-next-line no-await-in-loop
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      const subdomain = generateRandomSubdomain(totalLength);
      const domain = `${subdomain}.${rootDomain}`;

      logger.info(`Domain generation attempt ${attempt}/${maxRetries}`, {
        subdomain,
        domain,
        checkoutSessionId: orderData.checkoutSessionId,
      });

      // Generate bindDomainCap for this domain
      // eslint-disable-next-line no-await-in-loop
      const bindDomainCap = await this.generateBindCap({
        domain,
        checkoutSessionId: orderData.checkoutSessionId,
      });

      // Update order data with current domain info
      const updatedOrderData = {
        ...orderData,
        deliveryParams: {
          ...orderData.deliveryParams,
          customParams: {
            ...orderData.deliveryParams.customParams,
            subdomain,
            rootDomain,
            domain,
            bindDomainCap,
          },
        },
      };

      try {
        // eslint-disable-next-line no-await-in-loop
        const { headers, body } = await VendorAuth.signRequestWithHeaders(updatedOrderData);

        // eslint-disable-next-line no-await-in-loop
        const response = await fetch(url, {
          method: 'POST',
          headers,
          body,
        });

        if (response.ok) {
          logger.info('domain registration successful', {
            subdomain,
            domain,
            attempt,
            checkoutSessionId: orderData.checkoutSessionId,
          });
          return { response, subdomain, domain, bindDomainCap };
        }

        // Handle error response
        // eslint-disable-next-line no-await-in-loop
        const errorBody = await response.text();
        let errorData;

        try {
          errorData = JSON.parse(errorBody);
        } catch {
          errorData = { message: errorBody };
        }

        if (errorData.code === DOMAIN_CONFLICT_ERROR_CODE && attempt < maxRetries) {
          logger.warn('domain not available, retrying with new domain', {
            subdomain,
            domain,
            attempt,
            remainingAttempts: maxRetries - attempt,
            checkoutSessionId: orderData.checkoutSessionId,
          });
          // eslint-disable-next-line no-continue
          continue; // Try again with new domain
        }

        const errorMsg =
          errorData.code === DOMAIN_CONFLICT_ERROR_CODE
            ? `failed to find available domain after ${maxRetries} attempts`
            : `did names API error: ${response.status} ${response.statusText} - ${errorData.message || errorBody}`;

        logger.error('did names API error', {
          url,
          status: response.status,
          statusText: response.statusText,
          body: errorBody,
          subdomain,
          attempt,
          isDomainConflict: errorData.code === DOMAIN_CONFLICT_ERROR_CODE,
        });

        throw new Error(errorMsg);
      } catch (error: any) {
        lastError = error;

        // If it's a network error and not max retries, continue
        if (attempt < maxRetries && !error.message.includes('did names API error')) {
          logger.warn('network error during domain registration, retrying', {
            error: error.message,
            attempt,
            remainingAttempts: maxRetries - attempt,
            checkoutSessionId: orderData.checkoutSessionId,
          });
          // eslint-disable-next-line no-continue
          continue;
        }

        throw error;
      }
    }

    throw lastError || new Error('Unknown error occurred during domain registration');
  }

  /**
   * Generate bindDomainCap (binding capability) for domain authorization
   */
  private async generateBindCap({
    domain,
    checkoutSessionId,
  }: {
    domain: string;
    checkoutSessionId: string;
  }): Promise<any> {
    const now = Math.floor(Date.now() / 1000);
    const expireInMinutes = 30;

    const cap = {
      domain,
      checkoutSessionId,
      nbf: now, // not before
      exp: now + expireInMinutes * 60,
      nonce: uuidV4(),
    };

    const signature = toBase58(await wallet.sign(stableStringify(cap) || ''));

    return {
      cap,
      sig: signature,
    };
  }

  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 didnames order with domain binding', {
      checkoutSessionId: params.checkoutSessionId,
      productCode: params.productCode,
      customerId: params.customerId,
    });

    const vendorConfig = await this.getVendorConfig();

    try {
      const rootDomain = vendorConfig.metadata?.rootDomain;
      if (!rootDomain) {
        throw new Error('missing required metadata in didnames vendor: rootDomain');
      }

      logger.info('didnames vendor rootDomain', { rootDomain });

      const totalLength = parseDomainLength(vendorConfig.metadata?.subDomainLength, 8);

      // Prepare base order data
      const baseOrderData = {
        checkoutSessionId: params.checkoutSessionId,
        description: params.description,
        userInfo: params.userInfo,
        deliveryParams: {
          ...params.deliveryParams,
          customParams: {
            ...params.deliveryParams.customParams,
            years: 1,
            whoisPrivacy: true,
            checkoutSessionId: params.checkoutSessionId,
          },
        },
      };

      const url = formatVendorUrl(vendorConfig, '/api/vendor/deliveries');

      // Use the retry wrapper to submit order with domain conflict handling
      const { response, subdomain, domain, bindDomainCap } = await this.submitDomainOrderWithRetry(
        baseOrderData,
        url,
        rootDomain,
        totalLength
      );

      const didNamesResult = await response.json();

      const result: FulfillOrderResult = {
        orderId: didNamesResult.orderId,
        status: didNamesResult.status || 'pending',
        serviceUrl: `https://${subdomain}.${rootDomain}`, // User expects to access via custom domain
        estimatedTime: didNamesResult.estimatedTime || 300,
        message: didNamesResult.message || 'Domain binding order created successfully',
        vendorOrderId: didNamesResult.orderId,
        progress: didNamesResult.progress || 0,
        orderDetails: {
          productCode: params.productCode,
          customerId: params.customerId,
          amount: params.amount,
          currency: params.currency,
          quantity: params.quantity,
          invoiceId: params.invoiceId,
          customParams: {
            ...params.customParams,
            subdomain,
            rootDomain,
            domain: didNamesResult.domain || domain,
            nftDid: didNamesResult.nftDid,
            sessionId: params.customParams?.checkoutSessionId,
            bindDomainCap,
          },
        },
        installationInfo: {
          appId: didNamesResult.appId || `app_${subdomain.replace(/\./g, '_')}`,
          appUrl: `https://${subdomain}.${rootDomain}`, // Custom domain URL
          adminUrl: didNamesResult.adminUrl || `https://${subdomain}.${rootDomain}/admin`,
          status: didNamesResult.installationStatus || didNamesResult.status || 'installing',
          estimatedCompletionTime:
            didNamesResult.estimatedCompletionTime || new Date(Date.now() + 300000).toISOString(),
        },
      };

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

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

      throw error;
    }
  }

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

    const vendorConfig = await this.getVendorConfig();

    try {
      const returnRequest = {
        orderId: params.orderId,
        vendorOrderId: params.vendorOrderId,
        reason: params.reason,
        customParams: params.customParams,
      };

      const { headers, body } = await VendorAuth.signRequestWithHeaders(returnRequest);
      const url = formatVendorUrl(vendorConfig, '/api/vendor/return');
      logger.info('submitting domain return to DID Names', {
        url,
      });
      const response = await fetch(url, {
        method: 'POST',
        headers,
        body,
      });

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

      const didNamesResult = await response.json();

      return didNamesResult;
    } catch (error: any) {
      logger.error('Failed to process return request', {
        error: error.message,
        orderId: params.orderId,
      });

      throw error;
    }
  }

  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> {
    logger.info('Checking Didnames order status', {
      orderId: params.orderId,
    });

    try {
      const vendorConfig = await this.getVendorConfig();
      const url = formatVendorUrl(vendorConfig, `/api/vendor/orders/${params.orderId}/status`);

      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      });

      if (!response.ok) {
        logger.warn('DID Names order status check failed', {
          orderId: params.orderId,
          status: response.status,
        });
        return { status: 'processing' };
      }

      const orderStatus = await response.json();
      let status: 'processing' | 'completed' | 'failed' = 'processing';

      switch (orderStatus.status) {
        case 'completed':
        case 'active':
        case 'failed':
        case 'rejected':
        case 'error':
          status = 'failed';
          break;
        default:
          status = 'processing';
      }

      logger.info('Didnames order status checked', {
        orderId: params.orderId,
        status,
        didNamesStatus: orderStatus.status,
        domain: params.domain,
      });

      return { status };
    } catch (error: any) {
      logger.error('Failed to check didnames order status', {
        error: error.message,
        orderId: params.orderId,
      });

      throw error;
    }
  }

  async getOrder(vendor: ProductVendor, orderId: string): Promise<any> {
    const url = formatVendorUrl(vendor, `/api/vendor/orders/${orderId}`);

    const { headers } = await VendorAuth.signRequestWithHeaders({});
    const response = await fetch(url, { method: 'GET', headers });

    if (!response.ok) {
      logger.error('Failed to get didnames order', {
        url,
        orderId,
        status: response.status,
        statusText: response.statusText,
      });
      throw new Error(`Failed to get order: ${response.status} ${response.statusText}`);
    }

    const data = await response.json();
    return data;
  }

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

    logger.info('getting didnames order status', {
      url,
      orderId,
    });

    const { headers } = await VendorAuth.signRequestWithHeaders({});
    const response = await fetch(url, { method: 'GET', headers });

    logger.info('didnames order status response', {
      url,
      orderId,
      status: response.status,
      statusText: response.statusText,
    });

    if (!response.ok) {
      logger.error('Failed to get didnames order status', {
        url,
        orderId,
        status: response.status,
        statusText: response.statusText,
      });
      throw new Error(`Failed to get order status: ${response.status} ${response.statusText}`);
    }

    const data = await response.json();

    return data;
  }

  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());
  }
}
