/**
 * 飞奕云平台 API 客户端
 * 参考 region.ts 的编码风格：async/await、BizError、清晰的注释
 */

import * as crypto from 'crypto';
import { post, get } from '../util/request';
import { BizError } from '../util/bizError';
import { ERRORENUM } from '../config/errorEnum';
import { addData } from '../data/addData';
import { TABLENAME } from '../config/dbEnum';

// 全局变量缓存 token 和过期时间
let feiYiToken = "";
let expireTime = 0;
let getTokenTime = 0;

let isFetchingToken = false; // 避免并发获取 token 的标志位
let refreshTokenPromise: Promise<string> | null = null; // 用于避免并发刷新 token 的 Promise

/**
 * 飞奕云平台配置（建议从环境变量或配置文件中读取）
 */
const FEIYI_CONFIG = {
    baseUrl: 'https://m.achelp.cn/open',
    clientId: process.env.FEIYI_CLIENT_ID || '419636996764598272',     // 我方提供的 clientId
    secretKey: process.env.FEIYI_SECRET_KEY || '02fc75446b4544b3a02444a2b5f9be2b', // 我方提供的 appSecret
};

/**
 * 生成随机字符串 nonce
 * @param length 长度，默认 16
 */
function generateNonce(): string {
    return crypto.randomBytes(16).toString('hex');
}

/**
 * 生成签名 sign
 * 算法：
 * 1. 除去空值请求参数（accessKey 即使为空串也保留）
 * 2. 按照参数名的字母升序排列非空请求参数（包含 accessKey）
 * 3. 使用 URL 键值对格式拼接（key1=value1&key2=value2...）
 * 4. 拼接上 SecretKey 得到 stringSignTemp
 * 5. 对 stringSignTemp 进行 MD5 运算，并将结果所有字符转换为大写
 * @param params 请求参数对象
 * @param secretKey 密钥
 */
function generateSign(params: Record<string, any>, timestamp: string, nonce: string, secretKey: string, charset: string = 'UTF-8'): string {
    // 1. 过滤掉值为 null 或 undefined 的参数（空字符串保留）
    const filteredParams: Record<string, any> = {};
    for (const key in params) {
        if (params[key] !== null && params[key] !== undefined) {
            filteredParams[key] = params[key];
        }
    }
    // 2. 按 key 升序排序
    const sortedKeys = Object.keys(filteredParams).sort();
    // 3. 拼接成 key1=value1&key2=value2...
    const paramStr = sortedKeys.map(key => `${key}=${filteredParams[key]}`).join('&');
    // 4. 拼接timestamp、nonce和SecretKey
    const stringSignTemp = paramStr + `&timestamp=${timestamp}&nonce=${nonce}&SecretKey=${secretKey}`;
    console.log('MD5加密请求参数:', stringSignTemp);
    // 6. MD5 并转大写
    return crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase();
}

/**
 * 获取飞奕云平台 access_token
 * @returns {Promise<string>} access_token
 * @throws 如果请求失败或响应码非 00000，则抛出 BizError
 */
export async function getFeiYiToken(): Promise<string> {
    const url = `${FEIYI_CONFIG.baseUrl}/oauth2/openapi/token`;
    
    // 校验token是否有效，有效则直接返回
    const now = Date.now();
    if (feiYiToken && expireTime > now) {
        console.log(`使用缓存 token，剩余有效时间: ${(expireTime - now) / 1000} 秒`);
        return feiYiToken;
    }

    // 根据文档，获取 token 的请求参数
    const params = {
        AccessKey: '',   // 文档要求传空串
        clientId: FEIYI_CONFIG.clientId
    };
    // 生成签名
    const timestamp = Date.now().toString(); // 毫秒时间戳
    const nonce = generateNonce();
    const sign = generateSign(params, timestamp, nonce, FEIYI_CONFIG.secretKey);
    console.log('MD5加密签名:', sign);

    const requestBody = {
        ...params,
        timestamp: timestamp,
        nonce: nonce,
        sign: sign
    };

    let result: any;
    try {
        result = await post(url, requestBody, {});
    } catch (err) {
        throw new BizError(ERRORENUM.网络错误, `获取 token 网络失败: ${err.message}`);
    }
    console.log('获取 token 响应:', result?.code);

    // 检查响应结构：文档中返回 { code, data: { access_token } }
    if (!result || result.code !== '00000') {
        throw new BizError(ERRORENUM.第三方接口错误, `获取 token 失败: ${result?.message || '未知错误'}`);
    }

    const accessToken = result.data?.access_token;
    if (!accessToken) {
        throw new BizError(ERRORENUM.第三方接口错误, '响应中未包含 access_token');
    } else {
        // 更新全局 token 和过期时间
        feiYiToken = accessToken;
        getTokenTime = now;
        // 默认 2分钟 过期，如果响应中包含 expires_in 字段，则使用该值（单位毫秒）
        expireTime = now + (result.data.expires_in ? result.data.expires_in : 2 * 60 * 1000);
        console.log(`获取新 token: ${accessToken}, 过期时间: ${new Date(expireTime).toISOString()}`);
    }
    return accessToken;
}

/**
 * 接口路径 -> 中文描述映射
 */
const PATH_DESC_MAP: Record<string, string> = {
    '/openApi/gw/page': '查询网关设备列表',
    '/openApi/indoorUnit/list': '查询内机列表',
    '/openApi/indoorUnit/control': '控制内机开关',
    '/openApi/building/tree': '查询建筑物树结构',
    '/openApi/electricMeter/getAll': '查询电表列表',
    '/openApi/electricMeterState/list': '查询抄表记录',
    '/openApi/indoorUnit/event/switch': '查询内机开关机记录',
    '/openApi/indoorUnit/alarm/errorHis': '查询内机故障记录',
};

/**
 * 带 Token 的 POST 请求封装
 * 自动获取/刷新 token
 * @param path 接口路径（相对路径）
 * @param body 请求体
 * @param retry 是否重试（用于 token 过期重试）
 */
async function feiYiPost(path: string, body: any, retry: boolean = true): Promise<any> {
    const url = `${FEIYI_CONFIG.baseUrl}${path}`;
    const requestTime = new Date();

    // 获取 token
    const token = await getFeiYiToken();

    const headers = {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
    };

    let result: any;
    let responseStatus: string;
    let responseTime: Date;
    let dbStatus: number;

    try {
        result = await post(url, body, headers);
        responseTime = new Date();
        console.log(`请求 ${path} 成功，响应:`, result?.code);

        if (result.code === '00000') {
            dbStatus = 1;
            responseStatus = '正常';
        } else {
            dbStatus = 0;
            responseStatus = '请求报错';
        }
    } catch (err) {
        responseTime = new Date();
        dbStatus = 0;
        // 区分连接超时和其他网络错误
        if (err.code === 'ETIMEDOUT' || err.code === 'ECONNABORTED' || err.message?.includes('timeout')) {
            responseStatus = '连接超时';
        } else if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
            responseStatus = '连接失败';
        } else {
            responseStatus = '请求报错';
        }

        // 记录日志（网络异常，无响应数据）
        addData(TABLENAME.数据接入日志表, {
            partner_name: '飞奕',
            request_url: url,
            request_desc: PATH_DESC_MAP[path] || '',
            request_params: body,
            response_data: null,
            status: dbStatus,
            response_status: responseStatus,
            request_time: requestTime,
            response_time: responseTime,
        }).catch(e => console.error('写入数据接入日志失败:', e));

        throw new BizError(ERRORENUM.网络错误, `请求 ${path} 失败: ${err.message}`);
    }

    // 如果 token 过期，重试一次
    if (result.code !== '00000') {
        if (retry && (result.code === '401' || result.message?.includes('token'))) {
            return feiYiPost(path, body, false);
        }
        // 非 token 错误，记录日志后抛异常
        addData(TABLENAME.数据接入日志表, {
            partner_name: '飞奕',
            request_url: url,
            request_desc: PATH_DESC_MAP[path] || '',
            request_params: body,
            response_data: result,
            status: dbStatus,
            response_status: responseStatus,
            request_time: requestTime,
            response_time: responseTime,
        }).catch(e => console.error('写入数据接入日志失败:', e));

        throw new BizError(ERRORENUM.第三方接口错误, `接口 ${path} 调用失败: ${result.message}`);
    }

    // 成功也记录日志
    addData(TABLENAME.数据接入日志表, {
        partner_name: '飞奕',
        request_url: url,
        request_desc: PATH_DESC_MAP[path] || '',
        request_params: body,
        response_data: result,
        status: dbStatus,
        response_status: responseStatus,
        request_time: requestTime,
        response_time: responseTime,
    }).catch(e => console.error('写入数据接入日志失败:', e));

    return result;
}

/**
 * 获取网关设备列表（分页）
 * @param page 当前页码，默认 1
 * @param limit 每页条数，默认 10
 * @returns 网关设备数据，包含 rows、total 等
 */
export async function getGatewayDeviceList(page: number = 1, limit: number = 10): Promise<any> {
    const path = '/openApi/gw/page';
    const body = { page, limit };
    const result = await feiYiPost(path, body);
    // 返回 data 部分，包含 page, limit, total, rows
    return result.data;
}

/**
 * 内机列表分页查询
 * @param params 
 * @returns
{
	"code": "00000", //状态码 00000 为正常 其他为异常
	"message": "success", //返回状态 success 为成功， 其他为失败
	"data": { // 返回数据 
		"page": 1, //当前页码
		"limit": 0, //每页条数
		"total": 579, //总数
		"rows": [{
			"indoorUnitId": "98566729709518848", //内机 ID
			"roomId": "98452624688414720", //房间 ID
			"floorId": "98452624680026112", //楼层 ID
			"buildingId": "34390184208629760", //楼栋 ID
			"indoorUnitAddressFull": "11-1-1-1", //内机四段地址
			"onOffLock": 1, //是否开关锁定 1 锁定 0 未锁定
			"onOff": 0, //开关状态 1 开 0 关
			"workModeLock": 0, //是否工作模式锁定 1 锁定 0 未锁定
			"workMode": 2, //工作模式 1 制冷 2 制热 3 送风 4 除湿
			"tempSetLock": 0, //是否温度锁定 1 锁定 0 未锁定
			"tempSet": 16.0, // 设定温度
			"tempSetLo": 16.0, // 锁定下限温度
			"tempSetHi": 32.0, // 锁定下限温度
			"fanSpeed": 4, // 风速 1 高风 2 中风 4 低风
			"hasTimingTasks": 0, //是否有定时任务
			"alarmCode": "LOST", //空调故障码
			"roomTemp": 27 //室内温度(实际为空调回风温度，有一定的误差仅做参考) 
			"lockedSwitch": 0, // 锁定的开关：0 关 1 开
			"lockedMode": 0, // 锁定模式 1 制冷 2 制热 3 送风 4 除湿 10 制热/送风 11 制冷/送风 12 制冷/除湿 13 制冷/除湿/送风 14 除湿/送风
		}]
	}
}
 */
export async function getIndoorUnitList(params: any): Promise<any> {
    const path = '/openApi/indoorUnit/list';
    const body = { ...params };
    const result = await feiYiPost(path, body);
    return result.data;
}

/**
 * 空调内机控制
 * @param params 
 * @returns 
 */
export async function controlIndoorUnit(params: any): Promise<any> {
    const path = '/openApi/indoorUnit/control';
    if (!params.indoorUnitAddressFull || !Array.isArray(params.indoorUnitAddressFull) || params.indoorUnitAddressFull.length === 0) {
        console.error('控制内机: 设备编号不能为空');
        throw new BizError(ERRORENUM.参数错误, "设备编号不能为空");
    }
    const body = { ...params };
    const result = await feiYiPost(path, body); // {code:"", message:""}
    console.log('控制内机响应:', result?.code);
    let success = result && result.code === '00000'? true : false;
    return { success, data: result.message };
}

/**
 * 建筑物树结构查询
 * @param params 
 * @returns
{
	"code": "00000", //状态码 00000 为正常，其他为异常
	"message": "success", //状态信息 success 为成功，其他为异常
	"data": [{
		"buildingId": "97474765970866176", //建筑物 ID
		"parentId": "0", //建筑物父 ID
		"buildingName": "飞奕 1", // 建筑物名称
		"buildingType": 1, //建筑物类型 1 楼栋 2 楼层 3 房间
		"children": [ //子建筑物
			{
				"buildingId": "97474766339964928",
				"parentId": "97474765970866176",
				"buildingName": "1 层",
				"buildingType": 2,
				"children": [{
					"buildingId": "97474766696480768",
					"parentId": "97474766339964928",
					"buildingName": "1-0",
					"buildingType": 3,
					"children": []
				}]
			}
		]
	}],
	"timestamp": 1714290880301
}
 */
export async function getRegionTree(): Promise<any> {
    const path = '/openApi/building/tree';
    const body = { };
    const result = await feiYiPost(path, body);
    return result.data;
}

/**
 * 电表分页查询
 * @param params 
 * @returns
{
	"code": "00000", //状态码 00000 为成功，其他为失败
	"message": "success", //提示信息 success 为成功 其他为失败
	"data": {
		"page": 0, //当前页码
		"limit": 0, //每页条数
		"total": 6, //总条数
		"rows": [{
			"meterId": "110510431701041152", //电表 ID
			"gatewayComId": , //电表网关设备通信 ID
			"gatewayCode": , //电表网关设备编码
			"protocolType": "DLT645-2007", //电表协议类型
			"ammeterReadingAfterTransformationRatio": 6547.68, //电表变比后读数 
			"transformationRatio": 80.0, //电表变比
			"meterComId": "210505015447" //电表地址
		}, {
			"gatewayComId": , //电表网关设备通信 ID
			"gatewayCode": , //电表网关设备编码
			"meterId": "110510346153885697",
			"transformationRatio": 50.0,
			"meterComId": "210505015560"
		}]
	},
	"timestamp": 1713245734235
}
 */
export async function getElectricMaterList(params: any): Promise<any> {
    const path = '/openApi/electricMeter/getAll';
    const body = { ...params };
    const result = await feiYiPost(path, body);
    return result.data;
}

/**
 * 抄表记录查询
 * @param params 
 * @returns 
{
	"code": "00000", //状态码 00000 表示成功，其他为失败
	"message": "success", //提示信息 success 为成功，其他为失败
	"data": {
		"page": 1, //当前页码
		"limit": 0, //每页条数
		"total": 6, //总条数
		"rows": [{
			"electricMeterStateId": "112111594590437376", //抄表记录 ID
			"gatewayId": "97477557167063040", //主机设备 ID
			"gatewayCode": "fa000001400001240231212000200026" //主机设备编码
			"meterComId": "210505015595", //电表地址
			"gatewayComId": 11, // 电表网关通信 ID
			"transformationRatio": 100.0, //电表变比
			"powerConsumptionAfterTransformationRatio": 0.08, //电表变比后用电量
			"ammeterReadingAfterTransformationRatio": 19167.4, // 电表变比后读数
			"ammeterReadingBeforeTransformationRatio": 191.67, //电表变比前读数
			"energyLoss": 0.00, //损耗电量
			"deviceTime": 1706371200000, //设备时间
		}]
	},
	"timestamp": 1713145563759
}
 */
export async function getMaterStateList(params: any): Promise<any> {
    const path = '/openApi/electricMeterState/list';
    const body = { ...params };
    const result = await feiYiPost(path, body);
    return result.data;
}

/**
 * 内机开关机记录查询
 * @param params 
 * @returns 
{
	"code": "00000",
	"message": "success",
	"path": null,
	"data": {
		"page": 1,
		"limit": 0,
		"total": 278,
		"rows": [{
			"indoorUnitEventId": "140718238987321344", //内机事件 ID
			"indoorUnitId": "97478680942739586", //内机 ID
			"gatewayComId": 1, //主机通信 ID
			"roomId": "97474774841819136", //内机所在房间 ID
			"floorId": "97474774485303296", //内机所在楼层 ID
			"buildingId": "97474765970866176", //内机所在楼栋 ID
			"indoorUnitAddressFull": "1-8-128-16", //内机四段地址
			"onOff": 0, //内机开关标志 0 关 1 开
			"workMode": 4, //工作模式 1 制冷 2 制热 3 送风 4 除湿
			"tempSet": 18.0, //内机温度设置
			"fanSpeed": 2, //内机风速 风速 1 高风 2 中风 4 低风
			"createTime": 1713173801018 //事件发生时间
		}]
	},
	"timestamp": 1713173831195
}
 */
export async function getIndoorUnitStateList(params: any): Promise<any> {
    const path = '/openApi/indoorUnit/event/switch';
    const body = { ...params };
    const result = await feiYiPost(path, body);
    return result.data;
}

/**
 * 空调内机故障记录分页查询
 * @param params { page, limit, roomIds?, indoorUnitAddressFull?, alarmType?, occurrenceTime? }
 * @returns data 部分，包含 page, limit, total, rows
 */
export async function getIndoorUnitAlarmErrorHis(params: any): Promise<any> {
    const path = '/openApi/indoorUnit/alarm/errorHis';
    const body = { ...params };
    const result = await feiYiPost(path, body);
    return result.data;
}

/**
 * 故障码查询故障详情
 * 根据品牌编码和错误编码查询故障描述和解决方案
 * 接口文档：空调故障码对接服务与解析流程.pdf
 * @param brandCode 品牌编码，如 "1"=日立
 * @param errorCode 错误编码，如 "LOST"
 * @returns { errorInfo: string, scheme: string | null } 故障信息描述、解决方案
 */
export async function getFaultCodeInfo(brandCode: string, errorCode: string): Promise<{ errorInfo: string; scheme: string | null }> {
    const url = 'https://errorcode.feiyikj.cn/prod-api/feiyi/app/errorCode/info';
    const query = { brandCode, errorCode };
    let result: any;
    try {
        result = await get(url, query);
    } catch (err) {
        console.error(`[故障码查询] 请求失败 brandCode=${brandCode} errorCode=${errorCode}:`, err);
        return { errorInfo: '查询失败', scheme: null };
    }
    if (!result || result.code !== 200) {
        console.error(`[故障码查询] 接口返回异常 brandCode=${brandCode} errorCode=${errorCode}:`, result);
        return { errorInfo: '查询失败', scheme: null };
    }
    const data = result.data || [];
    if (data.length === 0) {
        console.warn(`[故障码查询] 未查到故障码信息 brandCode=${brandCode} errorCode=${errorCode}`);
        return { errorInfo: errorCode, scheme: null };
    }
    return {
        errorInfo: data[0].errorInfo || errorCode,
        scheme: data[0].scheme || null,
    };
}

/**
 * 故障类型映射（中文 -> 数字）
 */
let faultTypeMap: { [key: string]: number } = {
    "内机故障": 5,
    "未绑定房间": 15
};

/** 数字 -> 中文的反向映射 */
let faultTypeReverseMap: Map<number, string> | null = null;

/** 构建数字->中文的反向映射 */
function getFaultTypeReverseMap(): Map<number, string> {
    if (faultTypeReverseMap) return faultTypeReverseMap;
    faultTypeReverseMap = new Map();
    for (const [cn, num] of Object.entries(faultTypeMap)) {
        faultTypeReverseMap.set(num, cn);
    }
    return faultTypeReverseMap;
}

/**
 * 根据 alarmType 数字获取中文故障类型名称
 * @param alarmType 故障类型数字
 * @returns 中文名称，未匹配时返回原数字字符串
 */
export function getFaultTypeName(alarmType: number | string): string {
    const num = typeof alarmType === 'string' ? parseInt(alarmType, 10) : alarmType;
    return getFaultTypeReverseMap().get(num) || String(alarmType);
}