<?php
/**
 * 阿里云账单查询类
 * 基于现有 aliyun-openapi-php-sdk (BssOpenApi 2017-12-14)
 *
 * 提供：
 *  - queryAccountBalance()                    查询账户余额
 *  - queryInstanceBill($billingCycle, $opts)  查询实例账单明细（对应控制台 expense-detail-by-instance 页面）
 *  - queryMonthlyInstanceConsumption(...)     查询月实例消耗（注：可能已下线，未验证；推荐用 queryInstanceBill）
 *
 * 说明：
 *  - core 的 Config.php 未自动加载 bssopenapi，故在此手动 include 各 request 文件，
 *    与 OthAppPush 处理 Push request 的方式保持一致。
 *  - QueryMonthlyBill 接口已被阿里云下线（返回 InvalidAction.NotFound），改用 QueryInstanceBill。
 *  - QueryInstanceBillRequest 由本工程自行补充（SDK 未提供），放在 SDK 同级目录保持命名空间一致。
 */
include_once 'lib/aliyun-openapi-php-sdk/aliyun-php-sdk-core/Config.php';
include 'lib/aliyun-openapi-php-sdk/aliyun-php-sdk-bssopenapi/BssOpenApi/Request/V20171214/QueryAccountBalanceRequest.php';
include 'lib/aliyun-openapi-php-sdk/aliyun-php-sdk-bssopenapi/BssOpenApi/Request/V20171214/QueryInstanceBillRequest.php';
include 'lib/aliyun-openapi-php-sdk/aliyun-php-sdk-bssopenapi/BssOpenApi/Request/V20171214/QueryMonthlyInstanceConsumptionRequest.php';
use \BssOpenApi\Request\V20171214 as Bss;

class OthAliyunBill
{
	// 默认密钥，可在实例化时覆盖
	private $accessKeyId     = "LTAI5t74t9FweSbjdSzDQFrq";
	private $accessKeySecret = "NPjyRBeFuPIV3mzXldSjkvI5mMvj08";
	private $regionId        = "cn-hangzhou"; // BssOpenApi 统一使用 cn-hangzhou
	private $iClientProfile;

	function __construct($accessKeyId = '', $accessKeySecret = '')
	{
		if (!empty($accessKeyId))     $this->accessKeyId     = $accessKeyId;
		if (!empty($accessKeySecret)) $this->accessKeySecret = $accessKeySecret;
		$this->iClientProfile = DefaultProfile::getProfile($this->regionId, $this->accessKeyId, $this->accessKeySecret);

		//BssOpenApi 未在 SDK endpoints.xml 注册（xml 里产品名为 Bss，请求类用 BssOpenApi），
		//此处动态注册 endpoint，避免报 "Can not find endpoint to access."
		DefaultProfile::addEndpoint($this->regionId, $this->regionId, "BssOpenApi", "business.aliyuncs.com");
	}

	/**
	 * 创建 ACS Client
	 */
	private function getClient()
	{
		return new DefaultAcsClient($this->iClientProfile);
	}

	/**
	 * 统一调用封装（捕获异常，返回结构化结果）
	 * 成功: ['status'=>true,  'data'=>$response]
	 * 失败: ['status'=>false, 'msg'=>$message, 'code'=>$code]
	 */
	private function doRequest($request)
	{
		try {
			$client   = $this->getClient();
			$response = $client->getAcsResponse($request);
			return ['status' => true, 'data' => $response];
		} catch (\Exception $e) {
			return [
				'status' => false,
				'msg'    => $e->getMessage(),
				'code'   => $e->getCode(),
			];
		}
	}

	/**
	 * 查询账户余额
	 * @return array
	 */
	function queryAccountBalance()
	{
		$request = new Bss\QueryAccountBalanceRequest();
		return $this->doRequest($request);
	}

	/**
	 * 查询实例账单明细（对应控制台 expense-detail-by-instance 页面）
	 * @param  string $billingCycle 账期，例如 "2026-07"
	 * @param  array  $options
	 *     - productCode       string  产品代码，如 "rds"、"ecs"，可选
	 *     - subscriptionType  string  订阅类型：Subscription(预付费) / PayAsYouGo(后付费)
	 *     - productType       string  产品类型，可选
	 *     - isBillingItem     bool    是否按计费项查询，false=实例维度(默认)，true=计费项维度
	 *     - isHideZeroCharge  bool    是否过滤0元账单，默认 false
	 *     - pageNum           int     页码，默认 1
	 *     - pageSize          int     每页条数，默认 20，最大 300
	 *     - billOwnerId       int     资源归属账号 ID，可选
	 * @return array
	 */
	function queryInstanceBill($billingCycle, $options = [])
	{
		$request = new Bss\QueryInstanceBillRequest();
		$request->setBillingCycle($billingCycle);

		if (!empty($options['productCode']))      $request->setProductCode($options['productCode']);
		if (!empty($options['subscriptionType'])) $request->setSubscriptionType($options['subscriptionType']);
		if (!empty($options['productType']))      $request->setProductType($options['productType']);
		if (isset($options['isBillingItem']))     $request->setIsBillingItem($options['isBillingItem'] ? 'true' : 'false');
		if (isset($options['isHideZeroCharge']))  $request->setIsHideZeroCharge($options['isHideZeroCharge'] ? 'true' : 'false');
		if (!empty($options['billOwnerId']))      $request->setBillOwnerId($options['billOwnerId']);

		$pageNum  = !empty($options['pageNum'])  ? (int)$options['pageNum']  : 1;
		$pageSize = !empty($options['pageSize']) ? (int)$options['pageSize'] : 20;
		$request->setPageNum($pageNum);
		$request->setPageSize($pageSize);

		return $this->doRequest($request);
	}

	/**
	 * 查询月实例消耗（注：该接口可能已下线，未验证；推荐使用 queryInstanceBill）
	 * @param  string $billingCycle 账期，例如 "2026-07"
	 * @param  array  $options 同 queryInstanceBill
	 * @return array
	 */
	function queryMonthlyInstanceConsumption($billingCycle, $options = [])
	{
		$request = new Bss\QueryMonthlyInstanceConsumptionRequest();
		$request->setBillingCycle($billingCycle);

		if (!empty($options['productCode']))      $request->setProductCode($options['productCode']);
		if (!empty($options['subscriptionType'])) $request->setSubscriptionType($options['subscriptionType']);
		if (!empty($options['productType']))      $request->setProductType($options['productType']);
		if (!empty($options['ownerId']))          $request->setOwnerId($options['ownerId']);

		$pageNum  = !empty($options['pageNum'])  ? (int)$options['pageNum']  : 1;
		$pageSize = !empty($options['pageSize']) ? (int)$options['pageSize'] : 10;
		$request->setPageNum($pageNum);
		$request->setPageSize($pageSize);

		return $this->doRequest($request);
	}

	/**
	 * 同步单月实例账单到数据库（api_tongji.money_aliyun）
	 * 流程：先删该账期旧数据 → 分页拉取API → 批量插入
	 * @param  string $billingCycle 账期 YYYY-MM
	 * @return array ['status'=>bool, 'count'=>int, 'cycle'=>string, 'msg'=>string]
	 */
	function syncInstanceBillToDb($billingCycle)
	{
		$db = new BaseDb(mvc::$cfg['DB']['API_TJ']);

		//先删该账期旧数据（整账期重导，最可靠）
		$db->del('money_aliyun', sprintf(" and billing_cycle='%s'", LibFc::Escape($billingCycle)));

		$allRows    = [];
		$pageNum    = 1;
		$pageSize   = 300;
		$totalCount = 0;

		do {
			$res = $this->queryInstanceBill($billingCycle, [
				'pageNum'  => $pageNum,
				'pageSize' => $pageSize,
			]);

			if (!$res['status']) {
				return ['status'=>false, 'cycle'=>$billingCycle, 'msg'=>'API调用失败: '.$res['msg'], 'count'=>0];
			}

			//SDK返回的是SimpleXMLElement，转成数组统一处理（兼容单条/多条）
			$dataArr    = json_decode(json_encode($res['data']->Data), true);
			$totalCount = isset($dataArr['TotalCount']) ? (int)$dataArr['TotalCount'] : 0;

			$items = isset($dataArr['Items']['Item']) ? $dataArr['Items']['Item'] : [];
			if (isset($items['InstanceID'])) $items = [$items]; //单条时转二维

			foreach ($items as $item) {
				$allRows[] = $this->mapItemToRow($item, $dataArr);
			}

			$pageNum++;
			usleep(150000); //0.15秒，避免触发API限流(10次/秒)
		} while (count($allRows) < $totalCount && !empty($items));

		//批量插入（分批，每批100条；INSERT IGNORE 避免唯一键冲突导致整批失败）
		if (!empty($allRows)) {
			$fields = array_keys($allRows[0]);
			foreach (array_chunk($allRows, 100) as $chunk) {
				$db->addAnyIgnore('money_aliyun', $fields, $chunk);
			}
		}

		return ['status'=>true, 'cycle'=>$billingCycle, 'count'=>count($allRows), 'total'=>$totalCount];
	}

	/**
	 * API返回的Item → 数据库行（PascalCase → snake_case）
	 * @param array $item  单条账单（已转数组）
	 * @param array $data  Data层数据（含BillingCycle/AccountID等）
	 */
	private function mapItemToRow($item, $data)
	{
		$g = function($name) use ($data) {
			return isset($data[$name]) ? (string)$data[$name] : '';
		};
		$i = function($name) use ($item) {
			return isset($item[$name]) ? (string)$item[$name] : '';
		};
		$f = function($name) use ($item) {
			return isset($item[$name]) ? (float)$item[$name] : 0;
		};

		return [
			'billing_cycle'   => $g('BillingCycle'),
			'account_id'      => $g('AccountID'),
			'account_name'    => $g('AccountName'),
			'instance_id'     => $i('InstanceID'),
			'instance_config' => $i('InstanceConfig'),
			'instance_spec'   => $i('InstanceSpec'),
			'nick_name'       => $i('NickName'),
			'region'          => $i('Region'),
			'zone'            => $i('Zone'),
			'internet_ip'     => $i('InternetIP'),
			'intranet_ip'     => $i('IntranetIP'),
			'resource_group'  => $i('ResourceGroup'),
			'tag'             => $i('Tag'),
			'cost_unit'       => $i('CostUnit'),
			'product_code'    => $i('ProductCode'),
			'product_name'    => $i('ProductName'),
			'product_type'    => $i('ProductType'),
			'product_detail'  => $i('ProductDetail'),
			'pip_code'        => $i('PipCode'),
			'commodity_code'  => $i('CommodityCode'),
			'subscription_type' => $i('SubscriptionType'),
			'billing_type'    => $i('BillingType'),
			'billing_item'    => $i('BillingItem'),
			'bill_item'       => $i('Item'),
			'billing_date'    => $i('BillingDate'),
			'currency'        => $i('Currency'),
			'usage_amount'    => $f('Usage'),
			'usage_unit'      => $i('UsageUnit'),
			'service_period'  => $i('ServicePeriod'),
			'service_period_unit' => $i('ServicePeriodUnit'),
			'list_price'      => $i('ListPrice'),
			'list_price_unit' => $i('ListPriceUnit'),
			'pretax_gross_amount' => $f('PretaxGrossAmount'),
			'invoice_discount'    => $f('InvoiceDiscount'),
			'deducted_by_coupons' => $f('DeductedByCoupons'),
			'deducted_by_cash_coupons' => $f('DeductedByCashCoupons'),
			'deducted_by_prepaid_card'  => $f('DeductedByPrepaidCard'),
			'deducted_by_resource_package' => $i('DeductedByResourcePackage'),
			'pretax_amount'   => $f('PretaxAmount'),
			'payment_amount'  => $f('PaymentAmount'),
			'cash_amount'     => $f('CashAmount'),
			'adjust_amount'   => $f('AdjustAmount'),
			'outstanding_amount' => $f('OutstandingAmount'),
			'owner_id'        => $i('OwnerID'),
		];
	}
}
