import { Inject, Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BasicdataIotEntity } from '../../basicdata/entity/iot';
import { BaseSysParamEntity } from '../../base/entity/sys/param';
import * as _ from 'lodash';
import { PushCaptureTaskEntity } from '../../monitor/entity/capturetask';
import * as fs from 'fs';
import * as path from 'path';
import * as http from 'http';
import * as https from 'https';
import { Readable } from 'stream';

/**
 * 数据源定义信息服务
 */
@Provide()
export class CapturetaskService extends BaseService {
    @InjectEntityModel(BasicdataIotEntity)
    basicdataIotEntity: Repository<BasicdataIotEntity>;

    @InjectEntityModel(BaseSysParamEntity)
    baseSysParamEntity: Repository<BaseSysParamEntity>;

    @InjectEntityModel(PushCaptureTaskEntity)
    pushCaptureTaskEntity: Repository<PushCaptureTaskEntity>;

    /**
     * 给所有萤石云固定监控设备：即时抓图(captureType=1) → 下载到本地 → 落库 →  删除云端图片
     */
    async captureForAllDevices() {
        console.log('[Capture] ===== 开始 captureForAllDevices =====');

        // 1. token
        const tokenEntity = await this.baseSysParamEntity.findOneBy({ keyName: 'ys7.accessToken' });
        console.log('[Capture] tokenEntity=', tokenEntity?.id, tokenEntity?.keyName);
        if (!tokenEntity?.data) {
            console.error('[Capture] ❌ 萤石 accessToken 不存在，请先获取 Token');
            return;
        }
        const accessToken = tokenEntity.data as string;
        console.log('[Capture] accessToken 存在，长度=', accessToken.length);

        // 2. projectId
        let projectId: string;
        try {
            projectId = await this.getProjectId();
            console.log('[Capture] projectId=', projectId);
        } catch (e) {
            console.error('[Capture] ❌ 获取 projectId 失败', e);
            return;
        }

        // 3. 查设备
        const devices = await this.basicdataIotEntity.find({
            where: { iotTypeCode: '萤石云固定监控' },
        });
        console.log(`[Capture] 查询到设备数量=${devices.length}`);

        if (devices.length === 0) {
            console.warn('[Capture] ⚠️ 没有萤石云固定监控设备，直接结束');
            return;
        }

        for (const [index, device] of devices.entries()) {
            console.log(`\n[Capture] ===== 设备 ${index + 1}/${devices.length} =====`);
            const deviceSerial = device.iotCode;
            const fileId = `cap_${deviceSerial}_${Date.now()}`;
            try {
                console.log('[Capture] 准备调用萤石抓图接口');
                const params = new URLSearchParams({
                    accessToken,
                    deviceSerial,
                    channelNo: '1',
                    projectId,
                    fileId,
                    captureType: '1',
                });

                console.log('[Capture] 请求URL=https://open.ys7.com/api/open/cloud/v1/capture/save');
                console.log('[Capture] 请求参数=', params.toString());

                const resp = await fetch('https://open.ys7.com/api/open/cloud/v1/capture/save', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: params.toString(),
                });

                console.log('[Capture] HTTP STATUS=', resp.status);

                const json: any = await resp.json();
                console.log('[Capture] 萤石返回=', JSON.stringify(json));

                if (json.meta?.code !== 200) {
                    console.error(
                        `[Capture] ❌ 抓图失败 ${deviceSerial} code=${json.meta?.code} msg=${json.meta?.message}`
                    );
                    continue;
                }

                const picUrl: string = json.data;
                if (!picUrl) {
                    console.error('[Capture] ❌ 萤石返回 picUrl 为空');
                    continue;
                }
                console.log('[Capture] 抓图成功 picUrl=', picUrl);

                // 4. 本地目录
                const dateDir = new Date().toISOString().slice(0, 10).replace(/-/g, '');
                const localDir = path.join(process.cwd(), 'uploads', 'capture', dateDir);
                fs.mkdirSync(localDir, { recursive: true });
                console.log('[Capture] 本地目录=', localDir);

                const ext = path.extname(new URL(picUrl).pathname) || '.jpg';
                const fileName = `${deviceSerial}_${Date.now()}${ext}`;
                const dest = path.join(localDir, fileName);
                console.log('[Capture] 开始下载图片 dest=', dest);

                await this.downloadToFile(picUrl, dest);
                console.log('[Capture] 图片下载完成');

                const relativePath = path
                    .relative(path.join(process.cwd(), 'uploads'), dest)
                    .replace(/\\/g, '/');
                const localUrl = `/uploads/${relativePath}`;
                console.log('[Capture] localUrl=', localUrl);

                const entity = {
                    deviceSerial,
                    channelNo: '1',
                    captureTaskId: fileId,
                    cloudUrl: picUrl,
                    localPath: dest,
                    localUrl,
                    spaceId: projectId,
                    status: 'SUCCESS',
                    captureDate: dateDir,
                } as any;

                console.log('[Capture] 准备落库 entity=', JSON.stringify(entity));

                await this.pushCaptureTaskEntity.save(entity);
                console.log('[Capture] ✅ 落库成功');

                // 5. 删除云端
                try {
                    await this.deleteCloudCapture(projectId, fileId);
                    console.log('[Capture] ✅ 云端图片已删除');
                } catch (delErr) {
                    console.warn('[Capture] ⚠️ 云端删除失败', delErr);
                }

                console.log('[Capture] 等待 1500ms 后处理下一台设备');
                await new Promise(r => setTimeout(r, 1500));

            } catch (err) {
                console.error(`[Capture] ❌ 设备异常 ${deviceSerial}`, err);
            }
        }

        console.log('[Capture] ===== captureForAllDevices 执行结束 =====');
    }

    async deleteCloudCapture(projectId: string, fileId: string) {
        const tokenEntity = await this.baseSysParamEntity.findOneBy({ keyName: 'ys7.accessToken' });
        if (!tokenEntity?.data) {
            throw new Error('萤石 accessToken 不存在，请先获取 Token');
        }
        const accessToken = tokenEntity.data as string;
        const url = `https://open.ys7.com/api/open/cloud/v1/file?accessToken=${accessToken}&projectId=${projectId}&fileId=${encodeURIComponent(fileId)}`;
        const resp = await fetch(url, { method: 'DELETE' });
        const json: any = await resp.json();
        if (json.meta?.code !== 200) {
            throw new Error('删除云抓拍失败: ' + JSON.stringify(json));
        }
    }

    /**
     * 1. 获取所有萤石云固定监控设备的iotCode(iotCode就是deviceSerial) 2. 给每一个设备创建抓图任务(下发设备抓拍) 3. 下载到本地并落库
     * 在整个函数中完成所有的操作，确保每个步骤都能正确执行，并在出现错误时进行适当的处理和日志记录。
     */
    async dailyCaptureForAllDevices() {
        const devices = await this.basicdataIotEntity.find({
            where: {
                iotTypeCode: '萤石云固定监控',
            },
        });
        for (const device of devices) {
            const deviceSerial = device.iotCode;
            const channelNo = Number(device.iotChanel ?? 1);
            try {
                const existing = await this.pushCaptureTaskEntity.findOne({
                    where: {
                        deviceSerial,
                        status: 'TASK_CREATED',
                    },
                });
                let taskId: string;
                if (existing) {
                    taskId = existing.captureTaskId;
                    console.log(`[Capture] 任务已存在 ${deviceSerial} taskId=${taskId}`);
                } else {
                    const { taskId: newTaskId } = await this.createDeviceCaptureTask(
                        deviceSerial,
                        channelNo,
                    );
                    taskId = newTaskId;
                    await this.pushCaptureTaskEntity.save({
                        deviceSerial,
                        channelNo,
                        captureTaskId: taskId,
                        spaceId: await this.getSpaceID(),
                        status: 'TASK_CREATED',
                        captureDate: new Date().toISOString().slice(0, 10).replace(/-/g, ''),
                    });
                    console.log(`[Capture] 创建任务成功 ${deviceSerial} taskId=${taskId}`);
                }
                await this.downloadAndSaveCapture(deviceSerial, channelNo);
            } catch (err) {
                console.error(`[Capture] 设备失败 ${deviceSerial}`, err);
            }
        }
    }

    // ---------- 1. 创建抓图任务(下发设备抓拍) ----------
    async createDeviceCaptureTask(deviceSerial: string, channelNo = 1) {
        const spaceId = await this.getSpaceID();
        const tokenEntity = await this.baseSysParamEntity.findOneBy({ keyName: 'ys7.accessToken' });
        if (!tokenEntity?.data) {
            throw new Error('萤石 accessToken 不存在，请先获取 Token');
        }
        const accessToken = tokenEntity.data as string;
        const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
        const taskName = `daily_${deviceSerial}_${today}`;
        // 萤石「批量抓图任务」接口(结果写云空间)
        const params = new URLSearchParams({
            accessToken,
            taskName,
            deviceSerial,
            channelNo: String(channelNo),
            captureType: '1',
            resultSpaceId: spaceId,
            schedule: '0 0 8,12,18,20 * * ?',
            expireDays: '7',
        });
        const resp = await fetch('https://open.ys7.com/api/service/cloudrecord/pic/capture/task', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: params.toString(),
        });
        const json: any = await resp.json();
        if (json.meta?.code !== 200) throw new Error('create capture task fail: ' + JSON.stringify(json));
        return { taskId: json.data?.taskId, spaceId };
    }

    // ---------- 2. 查询任务产生的图片(媒资列表) ----------
    async queryCaptureImages(deviceSerial: string, spaceId: string) {
        const tokenEntity = await this.baseSysParamEntity.findOneBy({ keyName: 'ys7.accessToken' });
        if (!tokenEntity?.data) {
            throw new Error('萤石 accessToken 不存在，请先获取 Token');
        }
        const accessToken = tokenEntity.data as string;
        const params = new URLSearchParams({
            accessToken,
            deviceSerial,
            spaceId,
            startTime: this.dayStartTimestamp(),
            endTime: this.dayEndTimestamp(),
        });
        const resp = await fetch('https://open.ys7.com/api/open/cloud/v1/capture/list?' + params.toString());
        const json: any = await resp.json();
        return (json.data?.list || []) as Array<{ picUrl: string; captureTime: number; picId: string }>;
    }

    // ---------- 3. 下载到本地并落库 ----------
    async downloadAndSaveCapture(deviceSerial: string, channelNo = 0) {
        const spaceId = await this.getSpaceID();
        const imgs = await this.queryCaptureImages(deviceSerial, spaceId);
        const dateDir = new Date().toISOString().slice(0, 10).replace(/-/g, '');
        const localDir = path.join(process.cwd(), 'uploads', 'capture', dateDir);
        fs.mkdirSync(localDir, { recursive: true });

        for (const img of imgs) {
            const ext = path.extname(new URL(img.picUrl).pathname) || '.jpg';
            const fileName = `${deviceSerial}_${img.captureTime}${ext}`;
            const dest = path.join(localDir, fileName);
            try {
                await this.downloadToFile(img.picUrl, dest);
                await this.pushCaptureTaskEntity.save({
                    deviceSerial,
                    channelNo,
                    cloudUrl: img.picUrl,
                    localPath: dest,
                    spaceId,
                    status: 'SUCCESS',
                    captureDate: dateDir,
                } as any);
            } catch (e) {
                console.error('下载/落库失败:', img.picUrl, e);
            }
        }
    }

    async getProjectId(): Promise<string> {
        const e = await this.baseSysParamEntity.findOneBy({ keyName: 'ys7.captureProjectId' });
        if (e?.data) return e.data as string;
        // 没有就查列表取第一个，别自动建
        const tokenEntity = await this.baseSysParamEntity.findOneBy({ keyName: 'ys7.accessToken' });
        if (!tokenEntity?.data) {
            throw new Error('萤石 accessToken 不存在，请先获取 Token');
        }
        const accessToken = tokenEntity.data as string;
        const url = `https://open.ys7.com/api/open/cloud/v1/projects?accessToken=${encodeURIComponent(accessToken)}&pageNumber=0&pageSize=10`;
        console.log('[Capture] 请求 projects 接口');
        const r = await fetch(url);
        const j: any = await r.json();
        console.log('[Capture] projects 返回=', JSON.stringify(j));
        const pid = j.data?.[0]?.projectId;
        if (!pid) throw new Error('云抓拍项目不存在，请控制台创建');
        await this.baseSysParamEntity.save({ keyName: 'ys7.captureProjectId', data: pid, updateTime: new Date() });
        return pid;
    }

    async getSpaceID(): Promise<string> {
        const entity = await this.baseSysParamEntity.findOneBy({ keyName: 'ys7.storageSpaceID' });
        if (!entity ||
            !entity.data) {
            console.error('[Ys7Service] ys7.spaceID 未配置');
            throw new Error('ys7.spaceID 未配置');
        }
        return entity!.data as string;
    }

    // ---------- 工具：GET 下载文件到本地 ----------
    private downloadToFile(url: string, destPath: string): Promise<void> {
        return new Promise(async (resolve, reject) => {
            try {
                const resp = await fetch(url, { redirect: 'follow' });
                if (!resp.ok || !resp.body) {
                    reject(new Error(`download HTTP ${resp.status}`));
                    return;
                }
                const fileStream = fs.createWriteStream(destPath);
                const nodeStream = Readable.from(resp.body as any);
                nodeStream.on('error', reject);
                fileStream.on('finish', () => fileStream.close(() => resolve()));
                fileStream.on('error', reject);
                nodeStream.pipe(fileStream);
            } catch (err) {
                reject(err);
            }
        });
    }

    private dayStartTimestamp(): string {
        const now = new Date();
        const start = new Date(Date.UTC(
            now.getFullYear(),
            now.getMonth(),
            now.getDate(),
            0,
            0,
            0,
        ));
        return Math.floor((start.getTime() + 8 * 60 * 60 * 1000) / 1000).toString();
    }

    private dayEndTimestamp(): string {
        const now = new Date();
        const end = new Date(Date.UTC(
            now.getFullYear(),
            now.getMonth(),
            now.getDate(),
            23,
            59,
            59,
        ));
        return Math.floor((end.getTime() + 8 * 60 * 60 * 1000) / 1000).toString();
    }

}