/**
 * Account Export API
 * 
 * Generates a ZIP archive containing the user's complete account data
 * for migration to another Synapsis node.
 */

import { NextRequest, NextResponse } from 'next/server';
import { requireAuth, verifyPassword } from '@/lib/auth';
import { db } from '@/db';
import * as crypto from 'crypto';
import { decryptApiKey, deserializeEncryptedData } from '@/lib/bots/encryption';
import { canonicalize, verifySignedActionSignature } from '@/lib/crypto/user-signing';
import {
    decryptPrivateKey as decryptStoredPrivateKey,
    deserializeEncryptedKey,
} from '@/lib/crypto/private-key';
import {
    E2EE_KEY_BUNDLE_ACTION,
    e2eeKeyBundleSchema,
    signedUserActionSchema,
    type SignedUserAction,
} from '@/lib/e2ee/protocol';
import { encryptionKeyIdFromPublicKey } from '@/lib/e2ee/bundle-proof';

// We'll use a simple in-memory zip approach
// For production, consider using a streaming zip library

interface ExportManifest {
    version: '1.1';
    did: string;
    handle: string;
    sourceNode: string;
    exportedAt: string;
    expiresAt: string; // Export expiration timestamp
    publicKey: string;
    privateKeyEncrypted: string; // Original serialized AES-GCM key blob
    payloadDigestAlgorithm: 'sha256';
    payloadDigest: string; // Canonical digest of every non-manifest export field
    signature: string; // Proof of ownership
}

interface ExportProfile {
    displayName: string | null;
    bio: string | null;
    avatarUrl: string | null;
    headerUrl: string | null;
}

interface ExportPost {
    id: string;
    content: string;
    createdAt: string;
    replyToApId: string | null;
    media: { filename: string; url: string; altText: string | null; isIPFS?: boolean }[];
}

interface ExportFollowing {
    actorUrl: string;
    handle: string;
    isRemote?: boolean;
    displayName?: string | null;
    bio?: string | null;
    avatarUrl?: string | null;
}

interface ExportDMConversation {
    id: string;
    type: string;
    participant2Handle: string;
    lastMessageAt: string | null;
    lastMessagePreview: string | null;
    encryptionMode: string;
    e2eeActivatedAt: string | null;
    messages: ExportDMMessage[];
}

interface ExportDMMessage {
    senderHandle: string;
    senderDisplayName: string | null;
    senderAvatarUrl: string | null;
    senderNodeDomain: string | null;
    senderDid: string | null;
    content: string | null;
    protocolVersion: number;
    clientMessageId: string | null;
    encryptedEnvelope: string | null;
    e2eeSignature: string | null;
    e2eeActionNonce: string | null;
    e2eeActionTs: number | null;
    deliveredAt: string | null;
    readAt: string | null;
    createdAt: string;
}

interface ExportE2EEContinuityAnchor {
    did: string;
    keyId: string;
    keyVersion: number;
    publicKey: string;
    proofAction: SignedUserAction;
}

interface ExportBot {
    id: string;
    name: string;
    handle: string;
    bio: string | null;
    avatarUrl: string | null;
    headerUrl: string | null;
    personalityConfig: unknown;
    llmProvider: string;
    llmModel: string;
    llmApiKey: string; // Decrypted
    botPrivateKey: string; // Decrypted
    publicKey: string;
    scheduleConfig: unknown;
    autonomousMode: boolean;
    isActive: boolean;
    sources: unknown[];
    activityLogs: unknown[];
}

interface ExportPayload {
    profile: ExportProfile;
    posts: ExportPost[];
    following: ExportFollowing[];
    dms: ExportDMConversation[];
    bots: ExportBot[];
    e2eeKeyBundle: ExportE2EEContinuityAnchor | null;
}

/**
 * Sign the manifest to prove ownership
 */
function signManifest(manifest: Omit<ExportManifest, 'signature'>, privateKey: string): string {
    const data = canonicalize(manifest);
    const sign = crypto.createSign('sha256');
    sign.update(data);
    return sign.sign(privateKey, 'base64');
}

function digestExportPayload(payload: ExportPayload): string {
    return crypto.createHash('sha256').update(canonicalize(payload)).digest('hex');
}

function signingKeyMatchesPublicKey(privateKey: string, publicKey: string): boolean {
    try {
        const derivedPublicKey = crypto.createPublicKey(privateKey).export({ type: 'spki', format: 'der' });
        const expectedPublicKey = crypto.createPublicKey(publicKey).export({ type: 'spki', format: 'der' });
        return derivedPublicKey.length === expectedPublicKey.length
            && crypto.timingSafeEqual(derivedPublicKey, expectedPublicKey);
    } catch {
        return false;
    }
}

async function exportE2EEContinuityAnchor(
    row: {
        did: string;
        keyId: string;
        keyVersion: number;
        publicKey: string;
        proofAction: string;
    } | undefined,
    user: { did: string; handle: string; publicKey: string },
): Promise<ExportE2EEContinuityAnchor | null> {
    if (!row) return null;

    const proof = signedUserActionSchema.parse(JSON.parse(row.proofAction));
    const bundle = e2eeKeyBundleSchema.parse(proof.data);
    if (proof.action !== E2EE_KEY_BUNDLE_ACTION
        || proof.did !== user.did
        || proof.handle.toLowerCase() !== user.handle.toLowerCase()
        || row.did !== proof.did
        || row.keyId !== bundle.keyId
        || row.keyVersion !== bundle.version
        || row.publicKey !== bundle.publicKey
        || Math.abs(bundle.createdAt - proof.ts) > 5 * 60 * 1_000
        || Buffer.from(bundle.publicKey, 'base64url').length !== 32
        || Buffer.from(bundle.recoveryCommitment, 'base64url').length !== 32
        || await encryptionKeyIdFromPublicKey(bundle.publicKey) !== bundle.keyId
        || !await verifySignedActionSignature(proof, user.publicKey)) {
        throw new Error('Stored E2EE continuity proof is invalid');
    }

    return {
        did: row.did,
        keyId: row.keyId,
        keyVersion: row.keyVersion,
        publicKey: row.publicKey,
        proofAction: proof,
    };
}

export async function POST(req: NextRequest) {
    try {
        const user = await requireAuth();

        const body = await req.json();
        const { password } = body;

        if (!password) {
            return NextResponse.json({ error: 'Password required for export' }, { status: 400 });
        }

        // Verify password
        if (!user.passwordHash) {
            return NextResponse.json({ error: 'Account has no password set' }, { status: 400 });
        }

        const isValid = await verifyPassword(password, user.passwordHash);
        if (!isValid) {
            return NextResponse.json({ error: 'Invalid password' }, { status: 401 });
        }

        // Check if account has already moved
        if (user.movedTo) {
            return NextResponse.json({ error: 'This account has already been migrated' }, { status: 400 });
        }

        if (!user.privateKeyEncrypted) {
            return NextResponse.json({ error: 'Account signing key is unavailable' }, { status: 500 });
        }

        let signingPrivateKey: string;
        try {
            signingPrivateKey = decryptStoredPrivateKey(
                deserializeEncryptedKey(user.privateKeyEncrypted),
                password,
            );
        } catch {
            return NextResponse.json({ error: 'Account signing key could not be unlocked' }, { status: 500 });
        }
        if (!signingKeyMatchesPublicKey(signingPrivateKey, user.publicKey)) {
            return NextResponse.json({ error: 'Account signing key does not match this account' }, { status: 500 });
        }

        const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';

        // Fetch user's posts
        const userPosts = await db.query.posts.findMany({
            where: { userId: user.id },
            with: {
                media: true,
            },
            orderBy: (posts, { desc }) => [desc(posts.createdAt)],
        });

        // Fetch user's following list (local and remote)
        const userFollowing = await db.query.follows.findMany({
            where: { followerId: user.id },
            with: {
                following: true,
            },
        });

        const userRemoteFollowing = await db.query.remoteFollows.findMany({
            where: { followerId: user.id },
        });

        // Fetch DMs
        const userConversations = await db.query.chatConversations.findMany({
            where: { participant1Id: user.id },
            with: {
                messages: true
            }
        });

        const currentE2EEKeyBundle = await db.query.e2eeKeyBundles.findFirst({
            where: { userId: user.id },
        });
        const e2eeKeyBundle = await exportE2EEContinuityAnchor(currentE2EEKeyBundle, user);

        // Fetch Bots
        const userBots = await db.query.bots.findMany({
            where: { ownerId: user.id },
            with: {
                user: true,
                contentSources: true,
                activityLogs: {
                    limit: 50,
                    orderBy: (logs, { desc }) => [desc(logs.createdAt)]
                }
            }
        });

        // Build export data
        const exportPosts: ExportPost[] = userPosts.map(post => ({
            id: post.id,
            content: post.content,
            createdAt: post.createdAt.toISOString(),
            replyToApId: post.replyToId ? `https://${nodeDomain}/posts/${post.replyToId}` : null,
            media: (post.media || []).map((m, idx) => ({
                filename: `${post.id}_${idx}${getExtension(m.url)}`,
                url: m.url,
                altText: m.altText,
                isIPFS: m.url?.startsWith('ipfs://') || false,
            })),
        }));

        const exportFollowing: ExportFollowing[] = [
            // Local follows
            ...userFollowing.map(f => {
                const followingUser = f.following as { handle: string };
                return {
                    actorUrl: `https://${nodeDomain}/users/${followingUser.handle}`,
                    handle: followingUser.handle,
                    isRemote: false,
                };
            }),
            // Remote follows
            ...userRemoteFollowing.map(f => ({
                actorUrl: f.targetActorUrl,
                handle: f.targetHandle,
                isRemote: true,
                displayName: f.displayName,
                bio: f.bio,
                avatarUrl: f.avatarUrl,
            })),
        ];

        const profile: ExportProfile = {
            displayName: user.displayName,
            bio: user.bio,
            avatarUrl: user.avatarUrl,
            headerUrl: user.headerUrl,
        };

        const exportDMs: ExportDMConversation[] = userConversations.map(conv => ({
            id: conv.id,
            type: conv.type,
            participant2Handle: conv.participant2Handle,
            lastMessageAt: conv.lastMessageAt?.toISOString() || null,
            lastMessagePreview: conv.lastMessagePreview,
            encryptionMode: conv.encryptionMode,
            e2eeActivatedAt: conv.e2eeActivatedAt?.toISOString() || null,
            messages: conv.messages.map(msg => ({
                senderHandle: msg.senderHandle,
                senderDisplayName: msg.senderDisplayName,
                senderAvatarUrl: msg.senderAvatarUrl,
                senderNodeDomain: msg.senderNodeDomain,
                senderDid: msg.senderDid,
                content: msg.protocolVersion === 0 ? msg.content : null,
                protocolVersion: msg.protocolVersion,
                clientMessageId: msg.clientMessageId,
                encryptedEnvelope: msg.encryptedEnvelope,
                e2eeSignature: msg.e2eeSignature,
                e2eeActionNonce: msg.e2eeActionNonce,
                e2eeActionTs: msg.e2eeActionTs,
                deliveredAt: msg.deliveredAt?.toISOString() || null,
                readAt: msg.readAt?.toISOString() || null,
                createdAt: msg.createdAt.toISOString()
            }))
        }));

        const exportBots: ExportBot[] = userBots.map(bot => {
            // Decrypt bot keys
            let llmApiKey = '';
            let botPrivateKey = '';
            try {
                if (bot.llmApiKeyEncrypted) {
                    llmApiKey = decryptApiKey(deserializeEncryptedData(bot.llmApiKeyEncrypted));
                }
                if (bot.user?.privateKeyEncrypted) {
                    botPrivateKey = decryptApiKey(deserializeEncryptedData(bot.user.privateKeyEncrypted));
                }
            } catch (e) {
                console.error(`Failed to decrypt keys for bot ${bot.name}:`, e);
            }

            return {
                id: bot.id,
                name: bot.name,
                handle: bot.user.handle,
                bio: bot.user.bio,
                avatarUrl: bot.user.avatarUrl,
                headerUrl: bot.user.headerUrl,
                personalityConfig: JSON.parse(bot.personalityConfig),
                llmProvider: bot.llmProvider,
                llmModel: bot.llmModel,
                llmApiKey,
                botPrivateKey,
                publicKey: bot.user.publicKey,
                scheduleConfig: bot.scheduleConfig ? JSON.parse(bot.scheduleConfig) : null,
                autonomousMode: bot.autonomousMode,
                isActive: bot.isActive,
                sources: bot.contentSources.map(s => ({
                    type: s.type,
                    url: s.url,
                    subreddit: s.subreddit,
                    apiKeyEncrypted: s.apiKeyEncrypted, // These are specific to sources and might need their own decryption if they use AUTH_SECRET
                    sourceConfig: s.sourceConfig ? JSON.parse(s.sourceConfig) : null,
                    keywords: s.keywords ? JSON.parse(s.keywords) : null,
                    isActive: s.isActive
                })),
                activityLogs: bot.activityLogs.map(l => ({
                    action: l.action,
                    details: JSON.parse(l.details),
                    success: l.success,
                    errorMessage: l.errorMessage,
                    createdAt: l.createdAt.toISOString()
                }))
            };
        });

        const exportPayload: ExportPayload = {
            profile,
            posts: exportPosts,
            following: exportFollowing,
            dms: exportDMs,
            bots: exportBots,
            e2eeKeyBundle,
        };

        // Version 1.1 fails closed on older importers and binds a canonical
        // digest of every non-manifest field into the signed manifest.
        const exportedAt = new Date();
        const expiresAt = new Date(exportedAt.getTime() + 30 * 24 * 60 * 60 * 1000); // 30 days
        const manifestData: Omit<ExportManifest, 'signature'> = {
            version: '1.1',
            did: user.did,
            handle: user.handle,
            sourceNode: nodeDomain,
            exportedAt: exportedAt.toISOString(),
            expiresAt: expiresAt.toISOString(),
            publicKey: user.publicKey,
            privateKeyEncrypted: user.privateKeyEncrypted,
            payloadDigestAlgorithm: 'sha256',
            payloadDigest: digestExportPayload(exportPayload),
        };

        // Sign the manifest
        const signature = signManifest(manifestData, signingPrivateKey);
        const manifest: ExportManifest = { ...manifestData, signature };

        // Build the export package as JSON (ZIP would require additional library)
        // For MVP, we'll use a JSON format that can be easily converted to ZIP later
        const exportPackage = {
            manifest,
            ...exportPayload,
        };

        return NextResponse.json({
            success: true,
            export: exportPackage,
            stats: {
                posts: exportPosts.length,
                following: exportFollowing.length,
                dms: exportDMs.length,
                bots: exportBots.length,
                mediaFiles: exportPosts.reduce((sum, p) => sum + p.media.length, 0),
            },
        });

    } catch (error) {
        if (error instanceof Error && error.message === 'Authentication required') {
            return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
        }
        console.error('Export error:', error);
        return NextResponse.json({ error: 'Export failed' }, { status: 500 });
    }
}

function getExtension(url: string): string {
    const match = url.match(/\.([a-zA-Z0-9]+)(?:\?|$)/);
    return match ? `.${match[1]}` : '.bin';
}
