// eslint-disable-next-line import/no-extraneous-dependencies
import { SpaceClient, GetObjectCommand, PutObjectCommand } from '@blocklet/did-space-js';
import { blocklet, wallet } from './auth';
import logger from './logger';
import env from './env';
import { streamToString } from './util';

// Get user's DID Space endpoint
export const getEndpointAndSpaceDid = async (userDid: string): Promise<{ endpoint: string; spaceDid: string }> => {
  const { user } = await blocklet.getUser(userDid);
  if (!user) {
    throw new Error('User not found');
  }
  if (!user.didSpace.endpoint) {
    throw new Error(`DID Space endpoint is not set for user ${userDid}`);
  }
  return {
    endpoint: user.didSpace.endpoint,
    spaceDid: user.didSpace.did,
  };
};

// Initialize DID Space client
export const getSpaceClient = async (userDid: string, endpoint?: string) => {
  let spaceEndpoint = endpoint;
  if (!spaceEndpoint) {
    const result = await getEndpointAndSpaceDid(userDid);
    spaceEndpoint = result.endpoint;
  }
  return new SpaceClient({
    endpoint: spaceEndpoint,
    wallet,
  });
};

// Universal billing information format
export interface BillingInfo {
  tx_hash: string; // Transaction hash
  timestamp: number; // Transaction timestamp

  // Basic billing information
  invoice_id: string; // Invoice ID
  category: string; // Bill category
  description?: string; // Bill description: subscription payment, recharge, refund, etc.

  // Payment information
  amount: string; // Payment amount
  currency: {
    // Payment medium information
    symbol: string; // Currency symbol
    decimal: number; // Decimal places
    type: string; // Payment type (arcblock/ethereum)
    chain_id: string; // Chain ID
    explorer_host: string; // Explorer host
    explorer_tx_url: string; // Explorer transaction URL
  };

  // Related business information
  related?: {
    type: string; // Relation type (subscription/invoice)
    id: string; // Related entity ID
    name: string; // Related entity name
    status?: string; // Related entity status
    link?: string; // Link to the related entity
    period_start?: number; // Period start time
    period_end?: number; // Period end time
  };

  customer_did: string; // Customer DID
  tags?: string[]; // User defined tags
  app_pid: string; // App PID
  link?: string; // Link to the related entity
}

// Merge billing information, preserving user customizations
const mergeBillingInfo = (existing: BillingInfo, updated: Partial<BillingInfo>): BillingInfo => {
  return {
    ...(existing || {}),
    ...updated,
    // Preserve user customizations if they exist
    description: existing?.description || updated?.description,
    category: existing?.category || updated?.category || 'other',
    tags: Array.from(new Set([...(existing.tags || []), ...(updated.tags || [])])),
  };
};

// Upload or update billing information to DID Space
export const uploadBillingInfo = async (
  userDid: string,
  billingInfo: BillingInfo,
  endpoint?: string
): Promise<boolean> => {
  try {
    // Validate required fields
    if (!billingInfo.tx_hash || !billingInfo.invoice_id || !billingInfo.amount) {
      logger.error('Missing required fields in billingInfo');
      return false;
    }

    const client = await getSpaceClient(userDid, endpoint);

    const key = `txs/${billingInfo.tx_hash}/metadata.json`;

    let finalBillingInfo = {
      ...billingInfo,
      app_pid: billingInfo?.app_pid || env.appPid,
    };

    // Check existing bill
    const existingBill = await client.send(new GetObjectCommand({ key }));
    if (existingBill.statusCode === 200 && existingBill.data) {
      // Merge with existing data if force update
      const existing = JSON.parse(await streamToString(existingBill.data)) as BillingInfo;
      finalBillingInfo = mergeBillingInfo(existing, billingInfo);
      logger.info('Updating existing billing:', { userDid, txHash: billingInfo.tx_hash });
    }

    const payload = new PutObjectCommand({
      key,
      data: JSON.stringify(finalBillingInfo),
    });

    const result = await client.send(payload);

    if (result.statusCode !== 200) {
      logger.error('Upload billing info failed:', result);
      return false;
    }

    logger.info(existingBill.statusCode === 200 ? 'Bill updated:' : 'Bill created:', {
      userDid,
      txHash: billingInfo.tx_hash,
    });
    return true;
  } catch (error) {
    logger.error('Upload billing info error:', error);
    return false;
  }
};

// Get specific billing information by transaction hash
export const getBillingInfo = async (
  userDid: string,
  txHash: string,
  endpoint?: string
): Promise<BillingInfo | null> => {
  try {
    if (!txHash) {
      logger.error('Transaction hash is required');
      return null;
    }

    const client = await getSpaceClient(userDid, endpoint);
    const payload = new GetObjectCommand({
      key: `txs/${txHash}/metadata.json`,
    });

    const result = await client.send(payload);

    if (result.statusCode !== 200 || !result.data) {
      logger.debug('Bill not found:', { userDid, txHash });
      return null;
    }

    const billingInfo = JSON.parse(await streamToString(result.data)) as BillingInfo;

    // Validate parsed data
    if (!billingInfo.tx_hash || !billingInfo.invoice_id) {
      logger.error('Invalid billing data structure:', { userDid, txHash });
      return null;
    }

    return billingInfo;
  } catch (error) {
    logger.error('Get billing info error:', { userDid, txHash, error });
    return null;
  }
};

// Update specific fields of billing information
export const updateBillingInfo = async (
  userDid: string,
  txHash: string,
  updates: Partial<BillingInfo>,
  endpoint?: string
): Promise<boolean> => {
  try {
    const existing = await getBillingInfo(userDid, txHash, endpoint);
    if (!existing) {
      logger.error('Billing not found for update:', { userDid, txHash });
      return false;
    }

    const updated = mergeBillingInfo(existing, updates);
    if (!updated.tx_hash || !updated.invoice_id || !updated.amount) {
      logger.error('Invalid billing data structure:', { userDid, txHash });
      return false;
    }
    const result = await uploadBillingInfo(userDid, updated, endpoint);
    return result;
  } catch (error) {
    logger.error('Update billing info error:', { userDid, txHash, error });
    return false;
  }
};

// Batch upload/update billing information
export const uploadBillingInfoBatch = async (
  userDid: string,
  billingInfoList: BillingInfo[],
  endpoint?: string
): Promise<boolean[]> => {
  try {
    if (!Array.isArray(billingInfoList) || billingInfoList.length === 0) {
      logger.error('Invalid billingInfoList:', { userDid });
      return [];
    }

    // Validate all bills before upload
    const validBills = billingInfoList.filter((bill) => bill.tx_hash && bill.invoice_id && bill.amount);

    if (validBills.length !== billingInfoList.length) {
      logger.warn('Some bills are invalid and will be skipped');
    }

    const results = await Promise.all(
      validBills.map((billingInfo) => uploadBillingInfo(userDid, billingInfo, endpoint))
    );

    return results;
  } catch (error) {
    logger.error('Batch upload error:', { userDid, error });
    return [];
  }
};
