import {
    S3Client,
    GetObjectCommand,
    HeadBucketCommand,
    ListObjectsV2Command,
    ListBucketsCommand,
    CreateBucketCommand,
    DeleteObjectsCommand,
} from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';
import { PluginInstance, PluginContext } from '../../backend/src/core/types';
import type { StorageUploadPayload, StorageProviderInterface } from '../../backend/src/core/StorageProvider';

let storageProvider: any;
if (process.env.NODE_ENV === 'production') {
    storageProvider = require('../../dist/core/StorageProvider').storageProvider;
} else {
    storageProvider = require('../../backend/src/core/StorageProvider').storageProvider;
}
import sharp from 'sharp';
import fs from 'fs';
import path from 'path';

const VALID_CATEGORIES = ['documents', 'exports', 'posts', 'cards', 'partners', 'whatsapp', 'media'];

// Mapa de extensão → mimetype para o proxy
const MIME_MAP: Record<string, string> = {
    jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
    gif: 'image/gif',  webp: 'image/webp', svg: 'image/svg+xml',
    pdf: 'application/pdf', mp4: 'video/mp4', ogg: 'audio/ogg',
    mp3: 'audio/mpeg', doc: 'application/msword',
    docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    xls: 'application/vnd.ms-excel',
    xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
};

class UnifiedStorageProviderPlugin implements PluginInstance, StorageProviderInterface {
    manifest: any;
    private s3: S3Client | null = null;
    private bucket: string = '';
    private region: string = 'us-east-1';
    private localFallbackDir: string = path.resolve(process.cwd(), 'storage', 'local_fallback');
    private ctx!: PluginContext;
    private routesRegistered = false;

    async activate(ctx: PluginContext): Promise<void> {
        this.ctx = ctx;
        const cfg = ctx.config.get('UnifiedStorageProvider') || {};

        if (!fs.existsSync(this.localFallbackDir)) {
            fs.mkdirSync(this.localFallbackDir, { recursive: true });
        }

        const accessKey = cfg.wasabiAccessKey || process.env.WASABI_ACCESS_KEY;
        const secretKey = cfg.wasabiSecretKey || process.env.WASABI_SECRET_KEY;
        this.region   = (cfg.wasabiRegion   || process.env.WASABI_REGION   || 'us-east-1') as string;
        this.bucket   = (cfg.wasabiBucket   || process.env.WASABI_BUCKET   || '') as string;

        const rawEndpoint = (cfg.wasabiEndpoint || process.env.WASABI_ENDPOINT || 's3.wasabisys.com') as string;
        const endpoint    = rawEndpoint.startsWith('http') ? rawEndpoint : `https://${rawEndpoint}`;

        ctx.logger.info({ cfgKeys: Object.keys(cfg), hasKey: !!accessKey, hasBucket: !!this.bucket }, '[UnifiedStorage] activate debug');

        if (!accessKey || !secretKey || !this.bucket) {
            ctx.logger.warn('UnifiedStorageProvider: credenciais ou bucket não configurados — configure via admin');
        } else {
            this.s3 = new S3Client({
                region: this.region,
                endpoint,
                credentials: { accessKeyId: accessKey as string, secretAccessKey: secretKey as string },
                forcePathStyle: true,
            });

            storageProvider.register(this);
            ctx.logger.info(`UnifiedStorageProvider ativo — bucket: ${this.bucket} endpoint: ${endpoint}`);

            this.startSyncWorker();
        }

        if (!this.routesRegistered) {
            // ── Proxy: serve arquivos do Wasabi (ou fallback local) ────────────────
            ctx.app.get('/api/storage/view/*', async (req, res) => {
                const filePath = req.params[0];
                if (!filePath) return res.status(400).send('Path missing');

                try {
                    // 1. Fallback local
                    const localPath = path.join(this.localFallbackDir, filePath);
                    if (fs.existsSync(localPath)) {
                        const ext  = filePath.split('.').pop()?.toLowerCase() ?? '';
                        res.setHeader('Content-Type', MIME_MAP[ext] || 'application/octet-stream');
                        res.setHeader('Cache-Control', 'public, max-age=31536000');
                        res.setHeader('X-Storage-Source', 'local_fallback');
                        return res.send(fs.readFileSync(localPath));
                    }

                    // 2. Wasabi
                    if (!this.s3 || !this.bucket) {
                        return res.status(503).send('Storage não inicializado');
                    }

                    const result = await this.s3.send(new GetObjectCommand({
                        Bucket: this.bucket,
                        Key: filePath,
                    }));

                    const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
                    res.setHeader('Content-Type', result.ContentType || MIME_MAP[ext] || 'application/octet-stream');
                    res.setHeader('Cache-Control', 'public, max-age=31536000');
                    res.setHeader('X-Storage-Source', 'wasabi');

                    if (result.Body) {
                        const bytes = await (result.Body as any).transformToByteArray();
                        return res.send(Buffer.from(bytes));
                    }
                    return res.status(404).send('Body vazio');
                } catch {
                    return res.status(404).send('Arquivo não encontrado');
                }
            });

            // ── POST /api/storage/wasabi/list-buckets ──────────────────────────────
            // Lista buckets usando credenciais do body (sem exigir S3 pré-configurado)
            ctx.app.post('/api/storage/wasabi/list-buckets', async (req, res) => {
                const { accessKey, secretKey, region, endpoint } = req.body as Record<string, string>;
                if (!accessKey || !secretKey) {
                    return res.status(400).json({ error: 'Access Key e Secret Key são obrigatórios' });
                }
                try {
                    const ep  = (endpoint || 's3.wasabisys.com').startsWith('http')
                        ? endpoint || 's3.wasabisys.com'
                        : `https://${endpoint || 's3.wasabisys.com'}`;
                    const s3t = new S3Client({
                        region: region || 'us-east-1',
                        endpoint: ep,
                        credentials: { accessKeyId: accessKey, secretAccessKey: secretKey },
                        forcePathStyle: true,
                    });
                    const data    = await s3t.send(new ListBucketsCommand({}));
                    const buckets = (data.Buckets ?? []).map(b => ({ name: b.Name, createdAt: b.CreationDate }));
                    return res.json({ buckets });
                } catch (err: any) {
                    return res.status(400).json({ error: err.message });
                }
            });

            // ── POST /api/storage/wasabi/create-bucket ─────────────────────────────
            ctx.app.post('/api/storage/wasabi/create-bucket', async (req, res) => {
                const { accessKey, secretKey, region, endpoint, name } = req.body as Record<string, string>;
                if (!accessKey || !secretKey) return res.status(400).json({ error: 'Credenciais obrigatórias' });
                if (!name)                    return res.status(400).json({ error: 'Campo "name" obrigatório' });

                const safeName       = name.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
                const effectiveRegion = region || 'us-east-1';
                const ep = (endpoint || 's3.wasabisys.com').startsWith('http')
                    ? endpoint || 's3.wasabisys.com'
                    : `https://${endpoint || 's3.wasabisys.com'}`;

                try {
                    const s3t = new S3Client({
                        region: effectiveRegion,
                        endpoint: ep,
                        credentials: { accessKeyId: accessKey, secretAccessKey: secretKey },
                        forcePathStyle: true,
                    });
                    const params: any = { Bucket: safeName };
                    if (effectiveRegion !== 'us-east-1') {
                        params.CreateBucketConfiguration = { LocationConstraint: effectiveRegion };
                    }
                    await s3t.send(new CreateBucketCommand(params));
                    ctx.logger.info(`[Wasabi] Bucket criado: ${safeName}`);
                    return res.status(201).json({ ok: true, name: safeName });
                } catch (err: any) {
                    return res.status(500).json({ error: err.message });
                }
            });

            this.routesRegistered = true;
        }

        setInterval(() => { storageProvider.updateHealth(); }, 30000);
    }

    async deactivate(ctx: PluginContext): Promise<void> {
        this.s3 = null;
        ctx.logger.info('UnifiedStorageProvider desativado');
    }

    async checkHealth(): Promise<boolean> {
        return (await this.checkDetailedHealth()).healthy;
    }

    async checkDetailedHealth(): Promise<import('../../backend/src/core/StorageProvider').StorageHealthStatus> {
        if (!this.s3 || !this.bucket) {
            return {
                healthy: false,
                configured: false,
                reason: 'not_configured',
                message: 'Credenciais Wasabi não configuradas. Acesse Admin > Plugins > UnifiedStorageProvider.',
            };
        }
        try {
            await this.s3.send(new HeadBucketCommand({ Bucket: this.bucket }));
            return { healthy: true, configured: true, reason: 'ok', message: `Wasabi OK — bucket: ${this.bucket}` };
        } catch (err: any) {
            const code: string = err?.Code || err?.code || err?.name || '';
            const httpStatus: number = err?.$metadata?.httpStatusCode ?? 0;

            if (code === 'InvalidAccessKeyId' || code === 'SignatureDoesNotMatch') {
                return {
                    healthy: false, configured: true, reason: 'auth_error',
                    message: 'Chaves de acesso inválidas. Verifique Access Key e Secret Key no painel do Wasabi.',
                };
            }
            if (httpStatus === 403 || code === 'AccessDenied') {
                // 403 pode ser pagamento atrasado ou permissão negada
                return {
                    healthy: false, configured: true, reason: 'billing_error',
                    message: 'Acesso negado ao Wasabi (HTTP 403). Verifique se a conta está ativa e o pagamento em dia.',
                };
            }
            if (httpStatus === 404 || code === 'NoSuchBucket') {
                return {
                    healthy: false, configured: true, reason: 'bucket_not_found',
                    message: `Bucket "${this.bucket}" não encontrado. Verifique o nome do bucket no painel Wasabi.`,
                };
            }
            if (code === 'ENOTFOUND' || code === 'ECONNREFUSED' || code === 'NetworkingError') {
                return {
                    healthy: false, configured: true, reason: 'network_error',
                    message: 'Sem conectividade com o Wasabi. Verifique a rede e o endpoint configurado.',
                };
            }
            return {
                healthy: false, configured: true, reason: 'unknown',
                message: `Erro ao verificar Wasabi: ${err?.message ?? code}`,
            };
        }
    }

    async deletePrefix(prefix: string): Promise<{ deleted: number }> {
        let deletedCount = 0;

        // 1. Limpa do Wasabi
        if (this.s3 && this.bucket) {
            try {
                let isTruncated = true;
                let continuationToken: string | undefined;

                while (isTruncated) {
                    const listParams: any = {
                        Bucket: this.bucket,
                        Prefix: prefix,
                        MaxKeys: 1000,
                    };
                    if (continuationToken) {
                        listParams.ContinuationToken = continuationToken;
                    }

                    const listResult = await this.s3.send(new ListObjectsV2Command(listParams));
                    const objects = listResult.Contents ?? [];

                    if (objects.length > 0) {
                        const deleteParams = {
                            Bucket: this.bucket,
                            Delete: {
                                Objects: objects.map((obj) => ({ Key: obj.Key! })),
                                Quiet: true,
                            },
                        };
                        await this.s3.send(new DeleteObjectsCommand(deleteParams));
                        deletedCount += objects.length;
                        this.ctx.logger.info(`[UnifiedStorageProvider] ${objects.length} objetos deletados do Wasabi com prefixo ${prefix}`);
                    }

                    isTruncated = listResult.IsTruncated ?? false;
                    continuationToken = listResult.NextContinuationToken;
                }
            } catch (err: any) {
                this.ctx.logger.error(`[UnifiedStorageProvider] Erro ao deletar prefixo ${prefix} do Wasabi: ${err.message}`);
            }
        }

        // 2. Limpa do fallback local
        const localPath = path.join(this.localFallbackDir, prefix);
        if (fs.existsSync(localPath)) {
            try {
                const stat = fs.statSync(localPath);
                if (stat.isDirectory()) {
                    fs.rmSync(localPath, { recursive: true, force: true });
                    this.ctx.logger.info(`[UnifiedStorageProvider] Pasta local do fallback removida: ${localPath}`);
                } else {
                    fs.unlinkSync(localPath);
                    this.ctx.logger.info(`[UnifiedStorageProvider] Arquivo local do fallback removido: ${localPath}`);
                }
            } catch (err: any) {
                this.ctx.logger.error(`[UnifiedStorageProvider] Erro ao remover fallback local para prefixo ${prefix}: ${err.message}`);
            }
        }

        return { deleted: deletedCount };
    }

    async upload(payload: StorageUploadPayload): Promise<{ provider: string; path: string }> {
        const { category, file } = payload;
        if (!VALID_CATEGORIES.includes(category)) throw new Error(`Categoria inválida: ${category}`);

        let buffer      = file.buffer;
        let originalName = file.originalname;
        let mimetype    = file.mimetype;

        // Otimiza imagens grandes (>150 KB) para WebP
        if (mimetype.startsWith('image/') && !mimetype.includes('webp') && buffer.length > 150 * 1024) {
            try {
                buffer      = await sharp(buffer).resize(1200, 1200, { fit: 'inside', withoutEnlargement: true }).webp({ quality: 75 }).toBuffer();
                mimetype    = 'image/webp';
                originalName = originalName.replace(/\.[^.]+$/, '.webp');
                this.ctx.logger.debug(`[Optimizer] ${originalName} convertida para webp`);
            } catch (err: any) {
                this.ctx.logger.warn(`[Optimizer] falha: ${err.message}`);
            }
        }

        const safeBase   = originalName.replace(/[^a-zA-Z0-9._-]/g, '_');
        const fileName   = `${Date.now()}-${safeBase}`;

        // Path no bucket: whatsapp/{tenantId}/{instanceId}/{file}  ou  {category}/{file}
        const relativePath = (category === 'whatsapp' && payload.usuarioSis && payload.sessionJID)
            ? `whatsapp/${payload.usuarioSis}/${payload.sessionJID}/${fileName}`
            : `${category}/${fileName}`;

        if (!this.s3 || !this.bucket) {
            return this.saveToLocalFallback(relativePath, buffer, originalName, category);
        }

        try {
            await new Upload({
                client: this.s3,
                params: {
                    Bucket: this.bucket,
                    Key:    relativePath,
                    Body:   buffer,
                    ContentType: mimetype,
                },
            }).done();

            this.ctx.logger.debug(`[Wasabi] upload ok: ${relativePath}`);
            return { provider: 'wasabi', path: relativePath };

        } catch (err: any) {
            this.ctx.logger.error(`[Wasabi] upload falhou (${err.message}) — salvando local`);
            return this.saveToLocalFallback(relativePath, buffer, originalName, category);
        }
    }

    /** Salva buffer no diretório de fallback local, criando subpastas se necessário */
    private saveToLocalFallback(relativePath: string, buffer: Buffer, originalName: string, _category: string): { provider: string; path: string } {
        const dest = path.join(this.localFallbackDir, relativePath);
        fs.mkdirSync(path.dirname(dest), { recursive: true });
        fs.writeFileSync(dest, buffer);
        this.ctx.logger.debug(`[Fallback] salvo local: ${dest}`);
        return { provider: 'local_fallback', path: relativePath };
    }

    /** Sincroniza arquivos do fallback local para o Wasabi a cada 2 minutos */
    private startSyncWorker(): void {
        setInterval(async () => {
            if (!this.s3 || !this.bucket) return;
            await this.syncDir(this.localFallbackDir);
        }, 120_000);
    }

    private async syncDir(dir: string): Promise<void> {
        if (!fs.existsSync(dir)) return;

        for (const entry of fs.readdirSync(dir)) {
            const fullPath = path.join(dir, entry);
            const stat     = fs.statSync(fullPath);

            if (stat.isDirectory()) {
                await this.syncDir(fullPath);
                continue;
            }

            const relativePath = path.relative(this.localFallbackDir, fullPath).replace(/\\/g, '/');

            try {
                await new Upload({
                    client: this.s3!,
                    params: {
                        Bucket: this.bucket,
                        Key:    relativePath,
                        Body:   fs.readFileSync(fullPath),
                    },
                }).done();

                fs.unlinkSync(fullPath);
                this.ctx.logger.info(`[SyncWorker] enviado e removido: ${relativePath}`);
            } catch (err: any) {
                this.ctx.logger.warn(`[SyncWorker] falha em ${relativePath}: ${err.message}`);
            }
        }
    }
}

const instance = new UnifiedStorageProviderPlugin();
export default instance;
