import nodemailer from "nodemailer"
import { FastifyInstance } from "fastify"
import { and, eq } from "drizzle-orm"

import { encrypt, decrypt } from "../utils/crypt"
import { userCredentials } from "../../db/schema"
import { emailSyncService } from "../modules/email/email.sync.service"

// @ts-ignore
import MailComposer from "nodemailer/lib/mail-composer/index.js"
import { ImapFlow } from "imapflow"

export default async function emailAsUserRoutes(server: FastifyInstance) {
    const emailSync = emailSyncService(server)

    const encryptedValue = (value: unknown) => value ? decrypt(value as any) : null

    const accountResponse = (row: any) => ({
        id: row.id,
        createdAt: row.createdAt,
        updatedAt: row.updatedAt,
        userId: row.userId,
        tenantId: row.tenantId,
        type: row.type,
        email: encryptedValue(row.emailEncrypted),
        smtpHost: encryptedValue(row.smtpHostEncrypted),
        smtpPort: row.smtpPort ? Number(row.smtpPort) : null,
        smtpSsl: row.smtpSsl,
        imapHost: encryptedValue(row.imapHostEncrypted),
        imapPort: row.imapPort ? Number(row.imapPort) : null,
        imapSsl: row.imapSsl,
        hasPassword: Boolean(row.passwordEncrypted),
    })

    const accountCredentials = (row: any) => ({
        ...accountResponse(row),
        password: encryptedValue(row.passwordEncrypted),
    })

    const bodyValue = (body: any, camelKey: string, snakeKey: string) => body[camelKey] ?? body[snakeKey]

    const applyDownloadCorsHeaders = (req: any, reply: any) => {
        const origin = req.headers.origin
        if (
            origin
            && (
                /^http:\/\/(localhost|127\.0\.0\.1):\d+$/.test(origin)
                || origin === "https://beta.fedeo.de"
                || origin === "https://app.fedeo.de"
                || origin === "capacitor://localhost"
            )
        ) {
            reply.header("Access-Control-Allow-Origin", origin)
            reply.header("Access-Control-Allow-Credentials", "true")
            reply.header("Vary", "Origin")
        }

        reply.header("Access-Control-Expose-Headers", "Authorization, Content-Disposition, Content-Type, Content-Length")
    }

    const accountWhere = (tenantId: number, userId: string, id?: string) => {
        const conditions = [
            eq(userCredentials.tenantId, tenantId),
            eq(userCredentials.userId, userId),
            eq(userCredentials.type, "mail"),
        ]
        if (id) conditions.push(eq(userCredentials.id, id))
        return and(...conditions)
    }


    // ======================================================================
    // CREATE OR UPDATE EMAIL ACCOUNT
    // ======================================================================
    server.post("/email/accounts/:id?", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id?: string }

            const body = req.body as {
                email: string
                password: string
                smtpHost?: string
                smtpPort?: number
                smtpSsl?: boolean
                imapHost?: string
                imapPort?: number
                imapSsl?: boolean
                smtp_host?: string
                smtp_port?: number
                smtp_ssl?: boolean
                imap_host?: string
                imap_port?: number
                imap_ssl?: boolean
            }

            // -----------------------------
            // UPDATE EXISTING
            // -----------------------------
            if (id) {
                const rows = await server.db
                    .select({ id: userCredentials.id })
                    .from(userCredentials)
                    .where(accountWhere(req.user.tenant_id, req.user.user_id, id))
                    .limit(1)

                if (!rows[0]) return reply.code(404).send({ error: "Account not found" })

                const saveData = {
                    emailEncrypted: body.email ? encrypt(body.email) : undefined,
                    passwordEncrypted: body.password ? encrypt(body.password) : undefined,
                    smtpHostEncrypted: bodyValue(body, "smtpHost", "smtp_host") ? encrypt(bodyValue(body, "smtpHost", "smtp_host")) : undefined,
                    smtpPort: bodyValue(body, "smtpPort", "smtp_port"),
                    smtpSsl: bodyValue(body, "smtpSsl", "smtp_ssl"),
                    imapHostEncrypted: bodyValue(body, "imapHost", "imap_host") ? encrypt(bodyValue(body, "imapHost", "imap_host")) : undefined,
                    imapPort: bodyValue(body, "imapPort", "imap_port"),
                    imapSsl: bodyValue(body, "imapSsl", "imap_ssl"),
                    updatedAt: new Date(),
                }

                await server.db
                    .update(userCredentials)
                    //@ts-ignore
                    .set(saveData)
                    .where(accountWhere(req.user.tenant_id, req.user.user_id, id))

                return reply.send({ success: true })
            }

            // -----------------------------
            // CREATE NEW
            // -----------------------------
            const insertData = {
                userId: req.user.user_id,
                tenantId: req.user.tenant_id,
                type: "mail",

                emailEncrypted: encrypt(body.email),
                passwordEncrypted: encrypt(body.password),

                smtpHostEncrypted: encrypt(bodyValue(body, "smtpHost", "smtp_host")),
                smtpPort: bodyValue(body, "smtpPort", "smtp_port"),
                smtpSsl: bodyValue(body, "smtpSsl", "smtp_ssl"),

                imapHostEncrypted: encrypt(bodyValue(body, "imapHost", "imap_host")),
                imapPort: bodyValue(body, "imapPort", "imap_port"),
                imapSsl: bodyValue(body, "imapSsl", "imap_ssl"),
            }

            //@ts-ignore
            await server.db.insert(userCredentials).values(insertData)

            return reply.send({ success: true })
        } catch (err) {
            console.error("POST /email/accounts error:", err)
            return reply.code(500).send({ error: "Internal Server Error" })
        }
    })



    // ======================================================================
    // GET SINGLE OR ALL ACCOUNTS
    // ======================================================================
    server.get("/email/accounts/:id?", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id?: string }

            // ============================================================
            // LOAD SINGLE ACCOUNT
            // ============================================================
            if (id) {
                const rows = await server.db
                    .select()
                    .from(userCredentials)
                    .where(accountWhere(req.user.tenant_id, req.user.user_id, id))
                    .limit(1)

                const row = rows[0]
                if (!row) return reply.code(404).send({ error: "Not found" })

                return reply.send(accountResponse(row))
            }

            // ============================================================
            // LOAD ALL ACCOUNTS FOR TENANT
            // ============================================================
            const rows = await server.db
                .select()
                .from(userCredentials)
                .where(accountWhere(req.user.tenant_id, req.user.user_id))

            return reply.send(rows.map(accountResponse))

        } catch (err) {
            console.error("GET /email/accounts error:", err)
            return reply.code(500).send({ error: "Internal Server Error" })
        }
    })



    // ======================================================================
    // SEND EMAIL + SAVE IN IMAP SENT FOLDER
    // ======================================================================
    server.post("/email/send", async (req, reply) => {
        try {
            const body = req.body as {
                to: string
                cc?: string
                bcc?: string
                subject?: string
                text?: string
                html?: string
                attachments?: any
                account: string
            }

            // Fetch email credentials
            const rows = await server.db
                .select()
                .from(userCredentials)
                .where(accountWhere(req.user.tenant_id, req.user.user_id, body.account))
                .limit(1)

            const row = rows[0]
            if (!row) return reply.code(404).send({ error: "Account not found" })

            const accountData = accountCredentials(row)

            // -------------------------
            // SEND EMAIL VIA SMTP
            // -------------------------
            const transporter = nodemailer.createTransport({
                host: accountData.smtpHost,
                port: accountData.smtpPort,
                secure: accountData.smtpSsl,
                auth: {
                    user: accountData.email,
                    pass: accountData.password,
                },
            })

            const message = {
                from: accountData.email,
                to: body.to,
                cc: body.cc,
                bcc: body.bcc,
                subject: body.subject,
                html: body.html,
                text: body.text,
                attachments: body.attachments,
            }

            const info = await transporter.sendMail(message)

            // -------------------------
            // SAVE TO IMAP SENT FOLDER
            // -------------------------
            const imap = new ImapFlow({
                host: accountData.imapHost,
                port: accountData.imapPort,
                secure: accountData.imapSsl,
                auth: {
                    user: accountData.email,
                    pass: accountData.password,
                },
            })

            await imap.connect()

            const mail = new MailComposer(message)
            const raw = await mail.compile().build()

            let savedToSent = false
            for await (const mailbox of await imap.list()) {
                if (mailbox.specialUse === "\\Sent") {
                    await imap.mailboxOpen(mailbox.path)
                    await imap.append(mailbox.path, raw, ["\\Seen"])
                    savedToSent = true
                    break
                }
            }

            if (!savedToSent) {
                const sentFallbacks = ["Sent", "Gesendet", "INBOX.Sent"]
                for (const path of sentFallbacks) {
                    try {
                        await imap.append(path, raw, ["\\Seen"])
                        savedToSent = true
                        break
                    } catch (err) {
                        // Fallback wird nur genutzt, wenn der Ordner existiert.
                    }
                }
            }

            await imap.logout()

            return reply.send({ success: true })

        } catch (err) {
            console.error("POST /email/send error:", err)
            return reply.code(500).send({ error: "Failed to send email" })
        }
    })

    server.post("/email/accounts/:id/sync", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id: string }
            const body = (req.body || {}) as { mailbox?: string; limit?: number }

            const result = await emailSync.syncAccount(
                req.user.tenant_id,
                req.user.user_id,
                id,
                body,
            )

            return reply.send({ success: true, ...result })
        } catch (err: any) {
            req.log.error(err)
            return reply.code(500).send({ error: err.message || "E-Mail Sync fehlgeschlagen" })
        }
    })

    server.get("/email/accounts/:id/mailboxes", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id: string }
            return reply.send(await emailSync.listMailboxes(req.user.tenant_id, req.user.user_id, id))
        } catch (err: any) {
            req.log.error(err)
            return reply.code(500).send({ error: err.message || "Postfächer konnten nicht geladen werden" })
        }
    })

    server.get("/email/accounts/:id/messages", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id: string }
            const query = req.query as { mailbox?: string; limit?: string }

            return reply.send(await emailSync.listMessages(
                req.user.tenant_id,
                req.user.user_id,
                id,
                query.mailbox || "INBOX",
                Number(query.limit || 50),
            ))
        } catch (err: any) {
            req.log.error(err)
            return reply.code(500).send({ error: err.message || "E-Mails konnten nicht geladen werden" })
        }
    })

    server.get("/email/messages/:id", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id: string }
            const message = await emailSync.getMessage(req.user.tenant_id, req.user.user_id, id)
            if (!message) return reply.code(404).send({ error: "E-Mail nicht gefunden" })

            return reply.send(message)
        } catch (err: any) {
            req.log.error(err)
            return reply.code(500).send({ error: err.message || "E-Mail konnte nicht geladen werden" })
        }
    })

    server.post("/email/messages/:id/read", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id: string }
            const body = (req.body || {}) as { seen?: boolean }
            const message = await emailSync.setMessageSeen(
                req.user.tenant_id,
                req.user.user_id,
                id,
                body.seen !== false,
            )

            if (!message) return reply.code(404).send({ error: "E-Mail nicht gefunden" })

            return reply.send({ success: true, message })
        } catch (err: any) {
            req.log.error(err)
            return reply.code(500).send({ error: err.message || "Lesestatus konnte nicht synchronisiert werden" })
        }
    })

    server.post("/email/messages/:id/move", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id: string }
            const body = (req.body || {}) as { mailbox?: string }

            if (!body.mailbox) {
                return reply.code(400).send({ error: "Zielordner fehlt" })
            }

            const result = await emailSync.moveMessage(
                req.user.tenant_id,
                req.user.user_id,
                id,
                body.mailbox,
            )

            if (!result) return reply.code(404).send({ error: "E-Mail nicht gefunden" })
            return reply.send({ success: true, ...result })
        } catch (err: any) {
            req.log.error(err)
            return reply.code(500).send({ error: err.message || "E-Mail konnte nicht verschoben werden" })
        }
    })

    server.post("/email/messages/:id/archive", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id: string }
            const result = await emailSync.archiveMessage(req.user.tenant_id, req.user.user_id, id)

            if (!result) return reply.code(404).send({ error: "E-Mail nicht gefunden" })
            return reply.send({ success: true, ...result })
        } catch (err: any) {
            req.log.error(err)
            return reply.code(500).send({ error: err.message || "E-Mail konnte nicht archiviert werden" })
        }
    })

    server.delete("/email/messages/:id", async (req, reply) => {
        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id: string }
            const result = await emailSync.deleteMessage(req.user.tenant_id, req.user.user_id, id)

            if (!result) return reply.code(404).send({ error: "E-Mail nicht gefunden" })
            return reply.send({ success: true })
        } catch (err: any) {
            req.log.error(err)
            return reply.code(500).send({ error: err.message || "E-Mail konnte nicht gelöscht werden" })
        }
    })

    server.get("/email/attachments/:id/download", async (req, reply) => {
        applyDownloadCorsHeaders(req, reply)

        try {
            if (!req.user?.tenant_id) {
                return reply.code(400).send({ error: "No tenant selected" })
            }

            const { id } = req.params as { id: string }
            const attachment = await emailSync.getAttachmentContent(req.user.tenant_id, req.user.user_id, id)

            if (!attachment) return reply.code(404).send({ error: "Anhang nicht gefunden" })

            const buffer = Buffer.isBuffer(attachment.content)
                ? attachment.content
                : Buffer.from(attachment.content)
            const filename = attachment.filename.replace(/["\r\n]/g, "")

            reply.header("Content-Type", attachment.contentType || "application/octet-stream")
            reply.header("Content-Length", buffer.length)
            reply.header("Cache-Control", "no-store")
            reply.header("Content-Disposition", `attachment; filename="${filename}"`)
            return reply.send(buffer)
        } catch (err: any) {
            req.log.error(err)
            return reply.code(500).send({ error: err.message || "Anhang konnte nicht geladen werden" })
        }
    })

}
