import { APIRequestContext, request } from "@playwright/test"

declare const process: {
    env: Record<string, string | undefined>
    cwd(): string
}

declare function require(name: string): any

function detectMaildevUrlFromProject(): string | null {
    const projectName =
        process.env.PROJECT_NAME ||
        (() => {
            try {
                const fs = require("fs")
                const path = require("path")
                const envPath = path.resolve(process.cwd(), ".env")
                if (fs.existsSync(envPath)) {
                    const match = fs.readFileSync(envPath, "utf8").match(/PROJECT_NAME=(.+)/)
                    return match ? match[1].trim() : null
                }
            } catch {}

            return null
        })()

    return projectName ? `https://${projectName}-maildev.code.testversion.online` : null
}

/**
 * MailDev API helper for testing email flows.
 *
 * Configure via environment variables:
 * - MAILDEV_URL: MailDev web UI URL (default: https://${PROJECT_NAME}-maildev.code.testversion.online)
 * - MAILDEV_USER: Basic auth username (default: code)
 * - MAILDEV_PASS: Basic auth password
 */
const MAILDEV_URL = process.env.MAILDEV_URL || detectMaildevUrlFromProject() || "http://localhost:1080"
const MAILDEV_USER = process.env.MAILDEV_USER || "code"
const MAILDEV_PASS = process.env.MAILDEV_PASS || ""

export interface MailDevEmail {
    id: string
    subject: string
    html: string
    to: { address: string; name: string }[]
    from: { address: string; name: string }[]
    date: string
    time: string
    read: boolean
    headers: Record<string, string>
}

let maildevContext: APIRequestContext | null = null

async function getContext(): Promise<APIRequestContext> {
    if (!maildevContext) {
        const opts: any = {
            baseURL: MAILDEV_URL,
            ignoreHTTPSErrors: true,
        }
        if (MAILDEV_PASS) {
            opts.httpCredentials = {
                username: MAILDEV_USER,
                password: MAILDEV_PASS,
            }
        }
        maildevContext = await request.newContext(opts)
    }
    return maildevContext
}

/**
 * Get all emails from MailDev.
 */
export async function getAllEmails(): Promise<MailDevEmail[]> {
    const ctx = await getContext()
    const res = await ctx.get("/email")
    if (!res.ok()) {
        throw new Error(`MailDev API error: ${res.status()} ${res.statusText()}`)
    }
    return res.json()
}

/**
 * Delete all emails in MailDev.
 */
export async function deleteAllEmails(): Promise<void> {
    const ctx = await getContext()
    await ctx.delete("/email/all")
}

/**
 * Delete a specific email by ID.
 */
export async function deleteEmail(id: string): Promise<void> {
    const ctx = await getContext()
    await ctx.delete(`/email/${id}`)
}

/**
 * Wait for an email to arrive for a specific recipient.
 */
export async function waitForEmail(
    toEmail: string,
    options: {
        subject?: string | RegExp
        timeout?: number
        pollInterval?: number
    } = {}
): Promise<MailDevEmail> {
    const { subject, timeout = 5000, pollInterval = 250 } = options
    const start = Date.now()

    while (Date.now() - start < timeout) {
        const emails = await getAllEmails()
        const match = emails.find((e) => {
            const toMatch = e.to.some((t) => t.address.toLowerCase() === toEmail.toLowerCase())
            if (!toMatch) return false
            if (!subject) return true
            if (typeof subject === "string") return e.subject.includes(subject)
            return subject.test(e.subject)
        })
        if (match) return match
        await new Promise((r) => setTimeout(r, pollInterval))
    }

    throw new Error(`Timeout: No email to ${toEmail} received within ${timeout}ms`)
}

/**
 * Extract a 6-digit verification code from an email's HTML body.
 */
export function extractCode(email: MailDevEmail): string {
    const match = email.html.match(/>(\d{6})<\//)
    if (!match) {
        throw new Error(`No 6-digit code found in email "${email.subject}"`)
    }
    return match[1]
}

/**
 * Perform an action and capture the verification code from the resulting email.
 * Filters out emails that existed before the action was triggered.
 */
export async function captureCode(
    toEmail: string,
    action: () => Promise<void>,
    options: { subject?: string | RegExp; timeout?: number } = {}
): Promise<string> {
    const existingEmails = await getAllEmails()
    const existingIds = new Set(
        existingEmails
            .filter((e) => e.to.some((t) => t.address.toLowerCase() === toEmail.toLowerCase()))
            .map((e) => e.id)
    )

    await action()

    const { timeout = 5000, subject } = options
    const pollInterval = 250
    const start = Date.now()

    while (Date.now() - start < timeout) {
        const emails = await getAllEmails()
        const match = emails.find((e) => {
            if (existingIds.has(e.id)) return false
            const toMatch = e.to.some((t) => t.address.toLowerCase() === toEmail.toLowerCase())
            if (!toMatch) return false
            if (!subject) return true
            if (typeof subject === "string") return e.subject.includes(subject)
            return subject.test(e.subject)
        })
        if (match) return extractCode(match)
        await new Promise((r) => setTimeout(r, pollInterval))
    }

    throw new Error(`Timeout: No new email to ${toEmail} received within ${timeout}ms`)
}

/**
 * Dispose the MailDev API context. Call in globalTeardown.
 */
export async function disposeMailDev(): Promise<void> {
    if (maildevContext) {
        await maildevContext.dispose()
        maildevContext = null
    }
}
