import { createReadStream, statSync } from 'node:fs';
import { stat as fsStat, writeFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { Readable } from 'node:stream';
import { getContentType } from '@agentuity/server';
import { z } from 'zod';
import { getCommand } from "../../../command-prefix.js";
import { pathExists } from "../../../node-compat/fs.js";
import * as tui from "../../../tui.js";
import { createCommand } from "../../../types.js";
import { createStorageAdapter } from "./util.js";
function formatBytes(bytes) {
    if (bytes === undefined || bytes === null)
        return '—';
    if (bytes < 1024)
        return `${bytes} B`;
    if (bytes < 1024 * 1024)
        return `${(bytes / 1024).toFixed(1)} KB`;
    return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
function truncate(s, max) {
    if (s.length <= max)
        return s;
    return `${s.slice(0, max - 1)}…`;
}
// ── Upload ──────────────────────────────────────────────────────────────
const uploadSubcommand = createCommand({
    name: 'upload',
    aliases: ['up', 'put'],
    description: 'Upload a file attachment to a task',
    tags: ['mutating', 'slow', 'requires-auth'],
    requires: { auth: true },
    examples: [
        {
            command: getCommand('cloud task attachment upload task_abc123 ./report.pdf'),
            description: 'Upload a file to a task',
        },
    ],
    schema: {
        args: z.object({
            taskId: z.string().min(1).describe('the task ID to attach the file to'),
            file: z.string().min(1).describe('local file path to upload'),
        }),
        response: z.object({
            success: z.boolean().describe('Whether the operation succeeded'),
            attachment: z.object({
                id: z.string().describe('Attachment ID'),
                filename: z.string().describe('Filename'),
                content_type: z.string().optional().describe('Content type'),
                size: z.number().optional().describe('File size in bytes'),
            }),
            durationMs: z.number().describe('Operation duration in milliseconds'),
        }),
    },
    async handler(ctx) {
        const { args, options } = ctx;
        const started = Date.now();
        const storage = await createStorageAdapter(ctx);
        if (!(await pathExists(args.file))) {
            tui.fatal(`File not found: ${args.file}`);
        }
        const filename = basename(args.file);
        const contentType = getContentType(args.file) || 'application/octet-stream';
        const size = statSync(args.file).size;
        // Step 1: Get presigned upload URL
        const presign = await tui.spinner({
            message: 'Requesting upload URL',
            clearOnSuccess: true,
            callback: async () => {
                return storage.uploadAttachment(args.taskId, {
                    filename,
                    content_type: contentType,
                    size,
                });
            },
        });
        // Step 2: Upload file to presigned URL
        await tui.spinner({
            message: `Uploading ${filename}`,
            clearOnSuccess: true,
            callback: async () => {
                const uploadBody = Readable.toWeb(createReadStream(args.file));
                const response = await fetch(presign.presigned_url, {
                    method: 'PUT',
                    body: uploadBody,
                    headers: {
                        'Content-Type': contentType,
                        'Content-Length': String(size),
                    },
                    duplex: 'half',
                });
                if (!response.ok) {
                    tui.fatal(`Upload failed: ${response.statusText}`);
                }
            },
        });
        // Step 3: Confirm the upload
        const attachment = await tui.spinner({
            message: 'Confirming upload',
            clearOnSuccess: true,
            callback: async () => {
                return storage.confirmAttachment(presign.attachment.id);
            },
        });
        const durationMs = Date.now() - started;
        if (!options.json) {
            tui.success(`Attachment uploaded: ${tui.bold(attachment.id)}`);
            const tableData = {
                ID: attachment.id,
                Filename: attachment.filename,
                'Content Type': attachment.content_type ?? '—',
                Size: formatBytes(attachment.size),
                Task: attachment.task_id,
                Created: new Date(attachment.created_at).toLocaleString(),
            };
            tui.table([tableData], Object.keys(tableData), { layout: 'vertical', padStart: '  ' });
        }
        return {
            success: true,
            attachment: {
                id: attachment.id,
                filename: attachment.filename,
                content_type: attachment.content_type,
                size: attachment.size,
            },
            durationMs,
        };
    },
});
// ── List ────────────────────────────────────────────────────────────────
const listAttachmentsSubcommand = createCommand({
    name: 'list',
    aliases: ['ls'],
    description: 'List attachments for a task',
    tags: ['read-only', 'slow', 'requires-auth'],
    idempotent: true,
    requires: { auth: true },
    examples: [
        {
            command: getCommand('cloud task attachment list task_abc123'),
            description: 'List all attachments for a task',
        },
    ],
    schema: {
        args: z.object({
            taskId: z.string().min(1).describe('the task ID to list attachments for'),
        }),
        response: z.object({
            success: z.boolean().describe('Whether the operation succeeded'),
            attachments: z.array(z.object({
                id: z.string(),
                filename: z.string(),
                content_type: z.string().optional(),
                size: z.number().optional(),
                created_at: z.string(),
            })),
            total: z.number().describe('Total number of attachments'),
            durationMs: z.number().describe('Operation duration in milliseconds'),
        }),
    },
    async handler(ctx) {
        const { args, options } = ctx;
        const started = Date.now();
        const storage = await createStorageAdapter(ctx);
        const result = await storage.listAttachments(args.taskId);
        const durationMs = Date.now() - started;
        if (!options.json) {
            if (result.attachments.length === 0) {
                tui.info('No attachments found');
            }
            else {
                const tableData = result.attachments.map((att) => ({
                    ID: tui.muted(truncate(att.id, 28)),
                    Filename: truncate(att.filename, 40),
                    'Content Type': att.content_type ?? tui.muted('—'),
                    Size: formatBytes(att.size),
                    Created: new Date(att.created_at).toLocaleDateString(),
                }));
                tui.table(tableData, [
                    { name: 'ID', alignment: 'left' },
                    { name: 'Filename', alignment: 'left' },
                    { name: 'Content Type', alignment: 'left' },
                    { name: 'Size', alignment: 'right' },
                    { name: 'Created', alignment: 'left' },
                ]);
                tui.info(`${result.total} ${tui.plural(result.total, 'attachment', 'attachments')} (${durationMs.toFixed(1)}ms)`);
            }
        }
        return {
            success: true,
            attachments: result.attachments.map((att) => ({
                id: att.id,
                filename: att.filename,
                content_type: att.content_type,
                size: att.size,
                created_at: att.created_at,
            })),
            total: result.total,
            durationMs,
        };
    },
});
// ── Download ────────────────────────────────────────────────────────────
const downloadSubcommand = createCommand({
    name: 'download',
    aliases: ['dl', 'get'],
    description: 'Download a task attachment',
    tags: ['read-only', 'slow', 'requires-auth'],
    requires: { auth: true },
    examples: [
        {
            command: getCommand('cloud task attachment download att_abc123'),
            description: 'Download an attachment to the current directory',
        },
        {
            command: getCommand('cloud task attachment download att_abc123 --output ./downloads/'),
            description: 'Download an attachment to a specific directory',
        },
    ],
    schema: {
        args: z.object({
            attachmentId: z.string().min(1).describe('the attachment ID to download'),
        }),
        options: z.object({
            output: z
                .string()
                .optional()
                .describe('output file path or directory (defaults to current directory)'),
        }),
        response: z.object({
            success: z.boolean().describe('Whether the operation succeeded'),
            path: z.string().describe('Path where the file was saved'),
            size: z.number().describe('Downloaded file size in bytes'),
            durationMs: z.number().describe('Operation duration in milliseconds'),
        }),
    },
    async handler(ctx) {
        const { args, opts, options } = ctx;
        const started = Date.now();
        const storage = await createStorageAdapter(ctx);
        // Step 1: Get presigned download URL
        const presign = await tui.spinner({
            message: 'Requesting download URL',
            clearOnSuccess: true,
            callback: async () => {
                return storage.downloadAttachment(args.attachmentId);
            },
        });
        // Step 2: Download the file
        const response = await tui.spinner({
            message: 'Downloading',
            clearOnSuccess: true,
            callback: async () => {
                const res = await fetch(presign.presigned_url);
                if (!res.ok) {
                    tui.fatal(`Download failed: ${res.statusText}`);
                }
                return res;
            },
        });
        // Determine output path
        // Extract filename from Content-Disposition header or URL
        let filename = 'attachment';
        const disposition = response.headers.get('content-disposition');
        if (disposition) {
            const match = disposition.match(/filename[*]?=(?:UTF-8''|"?)([^";]+)/i);
            if (match?.[1]) {
                filename = decodeURIComponent(match[1].replace(/"/g, ''));
            }
        }
        else {
            // Try to extract filename from the presigned URL path
            const urlPath = new URL(presign.presigned_url).pathname;
            const urlFilename = basename(urlPath);
            if (urlFilename && urlFilename !== '/') {
                filename = decodeURIComponent(urlFilename);
            }
        }
        // Sanitize filename against path traversal
        filename = filename.replace(/\0/g, ''); // strip null bytes
        filename = filename.replace(/[/\\]/g, '_'); // replace path separators
        filename = filename.replace(/^\.+/, ''); // strip leading dots
        if (!filename || filename === '.' || filename === '..') {
            filename = 'attachment';
        }
        let outputPath;
        if (opts.output) {
            try {
                const stats = await fsStat(opts.output);
                if (stats.isDirectory()) {
                    outputPath = join(opts.output, filename);
                }
                else {
                    // It's an existing file — use it directly
                    outputPath = opts.output;
                }
            }
            catch {
                // Path doesn't exist — treat as target file path
                outputPath = opts.output;
            }
        }
        else {
            outputPath = join(process.cwd(), filename);
        }
        // Step 3: Write file to disk
        const size = await tui.spinner({
            message: `Saving to ${outputPath}`,
            clearOnSuccess: true,
            callback: async () => {
                const buffer = Buffer.from(await response.arrayBuffer());
                await writeFile(outputPath, buffer);
                return buffer.byteLength;
            },
        });
        const durationMs = Date.now() - started;
        if (!options.json) {
            tui.success(`Downloaded to ${tui.bold(outputPath)} (${formatBytes(size)})`);
        }
        return {
            success: true,
            path: outputPath,
            size,
            durationMs,
        };
    },
});
// ── Delete ──────────────────────────────────────────────────────────────
const deleteAttachmentSubcommand = createCommand({
    name: 'delete',
    aliases: ['rm', 'remove'],
    description: 'Delete a task attachment',
    tags: ['mutating', 'slow', 'requires-auth'],
    requires: { auth: true },
    examples: [
        {
            command: getCommand('cloud task attachment delete att_abc123'),
            description: 'Delete an attachment',
        },
    ],
    schema: {
        args: z.object({
            attachmentId: z.string().min(1).describe('the attachment ID to delete'),
        }),
        response: z.object({
            success: z.boolean().describe('Whether the operation succeeded'),
            attachmentId: z.string().describe('Deleted attachment ID'),
            durationMs: z.number().describe('Operation duration in milliseconds'),
        }),
    },
    async handler(ctx) {
        const { args, options } = ctx;
        const started = Date.now();
        const storage = await createStorageAdapter(ctx);
        await storage.deleteAttachment(args.attachmentId);
        const durationMs = Date.now() - started;
        if (!options.json) {
            tui.success(`Attachment deleted: ${tui.bold(args.attachmentId)}`);
        }
        return {
            success: true,
            attachmentId: args.attachmentId,
            durationMs,
        };
    },
});
// ── Parent command ──────────────────────────────────────────────────────
export const attachmentSubcommand = createCommand({
    name: 'attachment',
    aliases: ['attach', 'att'],
    description: 'Manage task attachments',
    tags: ['requires-auth'],
    requires: { auth: true },
    examples: [
        {
            command: getCommand('cloud task attachment upload task_abc123 ./report.pdf'),
            description: 'Upload a file to a task',
        },
        {
            command: getCommand('cloud task attachment list task_abc123'),
            description: 'List task attachments',
        },
        {
            command: getCommand('cloud task attachment download att_abc123'),
            description: 'Download an attachment',
        },
        {
            command: getCommand('cloud task attachment delete att_abc123'),
            description: 'Delete an attachment',
        },
    ],
    subcommands: [
        uploadSubcommand,
        listAttachmentsSubcommand,
        downloadSubcommand,
        deleteAttachmentSubcommand,
    ],
});
//# sourceMappingURL=attachment.js.map