type Env = {
    TURNSTILE_SECRET_KEY?: string;
    RESEND_API_KEY?: string;
    CONTACT_FROM_EMAIL?: string;
    CONTACT_TO_EMAIL?: string;
};

const jsonResponse = (data: Record<string, unknown>, status = 200) =>
    new Response(JSON.stringify(data), {
        status,
        headers: { "content-type": "application/json" },
    });

const fetchWithTimeout = async (
    input: RequestInfo | URL,
    init: RequestInit,
    timeoutMs: number,
    label: string,
) => {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), timeoutMs);
    try {
        return await fetch(input, { ...init, signal: controller.signal });
    } catch (error) {
        const message =
            error instanceof Error ? error.message : "Unknown error";
        throw new Error(`${label} request failed: ${message}`);
    } finally {
        clearTimeout(timeout);
    }
};

const readTextField = (value: string | null) => (value ?? "").trim();
const toTitleCase = (value: string) =>
    value
        .replace(/_/g, " ")
        .replace(/\b\w/g, (char) => char.toUpperCase());

const parseFormFields = async (request: Request) => {
    const contentType = request.headers.get("content-type") ?? "";
    const data = new Map<string, string[]>();

    const appendValue = (key: string, value: string | null) => {
        const normalized = readTextField(value);
        if (!normalized) return;
        data.set(key, [...(data.get(key) ?? []), normalized]);
    };

    if (contentType.includes("application/x-www-form-urlencoded")) {
        const bodyText = await request.text();
        const params = new URLSearchParams(bodyText);
        for (const [key, value] of params.entries()) {
            appendValue(key, value);
        }
        return data;
    }

    const formData = await request.formData();
    for (const [key, value] of formData.entries()) {
        appendValue(key, typeof value === "string" ? value : value.name);
    }
    return data;
};

const formatValue = (values: string[] | undefined) => (values ?? []).join(", ");

const labelMap: Record<string, string> = {
    filled_by: "Who is filling in the form",
    participant_first_names: "Participant first name(s)",
    participant_last_name: "Participant last name",
    preferred_name: "Preferred name or nickname",
    preferred_pronouns: "Preferred pronouns",
    date_of_birth: "Date of birth",
    sex: "Sex",
    languages_spoken: "Languages used every day",
    address_line_1: "Address line 1",
    address_line_2: "Address line 2",
    postcode: "Postcode",
    telephone: "Telephone",
    mobile: "Mobile",
    email: "Email",
    activity_experience: "Previous activity experience",
    allergies: "Allergies",
    triggers: "Known triggers",
    simple_instructions: "Would prefer simple instructions",
    learning_disability: "Learning disability",
    neurodiverse: "Neurodiverse",
    sensitive_to_crowds: "Sensitive to strangers or crowds",
    sensory_sensitivities: "Sensitive to light, temperature, noise or clothing",
    anxiety_low_mood: "Anxiety, depression or low mood",
    panic_attacks: "Panic attacks",
    speech_difficulties: "Difficulty with speech or being understood",
    communication_system: "Uses a communication system",
    hearing_aid: "Hearing aid",
    cochlear_implant: "Cochlear implant",
    bsl: "Uses BSL",
    blind: "Blind",
    partially_sighted: "Partially sighted",
    dexterity_difficulties: "Dexterity difficulties",
    balance_problems: "Problems with balance",
    weight_through_feet: "Can take weight through feet",
    help_walking: "Needs help with walking",
    steps_mounting_block: "Can walk up a few steps",
    wheelchair_user: "Wheelchair user",
    walking_aids: "Uses walking aids or supports",
    prosthetic_limb: "Wears a prosthetic limb",
    long_term_pain: "Long term pain",
    breathing_stamina: "Breathing or stamina difficulties",
    epilepsy: "Epilepsy or controlled by medication",
    condition_details: "Disability or medical condition details",
    participant_height: "Height",
    height_unit: "Height unit",
    participant_weight: "Weight",
    weight_unit: "Weight unit",
    interested_in_competing: "Interested in competing",
    been_classified: "Has been classified",
    classification_details: "Classification",
    classification_date: "Classification date",
    emergency_contact_name: "Emergency contact name and relationship",
    emergency_contact_phone: "Emergency contact number",
    emergency_contact_consent: "Emergency contact consent",
    declaration_confirm: "Declaration confirmation",
    provide_medical_details: "Agrees to provide further medical details if requested",
    notify_changes: "Will notify RDA of changes",
    risk_acknowledgement: "Accepts horse-related risk acknowledgement",
    vaulting_acknowledgement: "Vaulting risk acknowledgement",
    photos_consent: "Photo and video consent",
    newsletter_consent: "Newsletter consent",
    declaration_signature: "Signature name",
    declaration_date: "Declaration date",
    guardian_name: "Parent or legal guardian name",
    guardian_relationship: "Relationship to applicant",
    guardian_address_line_1: "Guardian address line 1",
    guardian_address_line_2: "Guardian address line 2",
    guardian_postcode: "Guardian postcode",
    guardian_telephone: "Guardian telephone",
    guardian_mobile: "Guardian mobile",
};

const requiredFields = [
    "filled_by",
    "participant_first_names",
    "participant_last_name",
    "date_of_birth",
    "address_line_1",
    "postcode",
    "email",
    "emergency_contact_name",
    "emergency_contact_phone",
    "declaration_confirm",
    "photos_consent",
];

const sectionOrder = [
    {
        title: "Applicant",
        fields: [
            "filled_by",
            "participant_first_names",
            "participant_last_name",
            "preferred_name",
            "preferred_pronouns",
            "date_of_birth",
            "sex",
            "languages_spoken",
            "address_line_1",
            "address_line_2",
            "postcode",
            "telephone",
            "mobile",
            "email",
            "activity_experience",
        ],
    },
    {
        title: "Specific Information",
        fields: [
            "allergies",
            "triggers",
            "simple_instructions",
            "learning_disability",
            "neurodiverse",
            "sensitive_to_crowds",
            "sensory_sensitivities",
            "anxiety_low_mood",
            "panic_attacks",
            "speech_difficulties",
            "communication_system",
            "hearing_aid",
            "cochlear_implant",
            "bsl",
            "blind",
            "partially_sighted",
            "dexterity_difficulties",
            "balance_problems",
            "weight_through_feet",
            "help_walking",
            "steps_mounting_block",
            "wheelchair_user",
            "walking_aids",
            "prosthetic_limb",
            "long_term_pain",
            "breathing_stamina",
            "epilepsy",
            "condition_details",
            "participant_height",
            "height_unit",
            "participant_weight",
            "weight_unit",
        ],
    },
    {
        title: "Additional Information",
        fields: [
            "interested_in_competing",
            "been_classified",
            "classification_details",
            "classification_date",
        ],
    },
    {
        title: "Emergency Contact",
        fields: [
            "emergency_contact_name",
            "emergency_contact_phone",
            "emergency_contact_consent",
        ],
    },
    {
        title: "Declaration",
        fields: [
            "declaration_confirm",
            "provide_medical_details",
            "notify_changes",
            "risk_acknowledgement",
            "vaulting_acknowledgement",
            "photos_consent",
            "newsletter_consent",
            "declaration_signature",
            "declaration_date",
        ],
    },
    {
        title: "Parent or Legal Guardian",
        fields: [
            "guardian_name",
            "guardian_relationship",
            "guardian_address_line_1",
            "guardian_address_line_2",
            "guardian_postcode",
            "guardian_telephone",
            "guardian_mobile",
        ],
    },
];

const handlePost: PagesFunction<Env> = async ({ request, env }) => {
    const fields = await parseFormFields(request);

    const missingRequired = requiredFields.filter(
        (field) => !formatValue(fields.get(field)),
    );
    if (missingRequired.length > 0) {
        return jsonResponse(
            {
                ok: false,
                error: "Missing required fields.",
                details: missingRequired.map((field) => labelMap[field] ?? field).join(", "),
            },
            400,
        );
    }

    if (!env.TURNSTILE_SECRET_KEY) {
        return jsonResponse(
            { ok: false, error: "Turnstile secret key not configured." },
            500,
        );
    }

    const turnstileToken = formatValue(fields.get("cf-turnstile-response"));
    if (!turnstileToken) {
        return jsonResponse(
            { ok: false, error: "Turnstile verification failed." },
            400,
        );
    }

    const verifyBody = new URLSearchParams({
        secret: env.TURNSTILE_SECRET_KEY,
        response: turnstileToken,
        remoteip: request.headers.get("CF-Connecting-IP") ?? "",
    });

    const verifyResponse = await fetchWithTimeout(
        "https://challenges.cloudflare.com/turnstile/v0/siteverify",
        {
            method: "POST",
            headers: { "content-type": "application/x-www-form-urlencoded" },
            body: verifyBody,
        },
        8000,
        "Turnstile",
    );
    const verifyResult = (await verifyResponse.json()) as {
        success?: boolean;
        "error-codes"?: string[];
        messages?: string[];
        hostname?: string;
        action?: string;
    };

    if (!verifyResult.success) {
        const details = [
            verifyResult["error-codes"]?.length
                ? `codes=${verifyResult["error-codes"].join(",")}`
                : "",
            verifyResult.messages?.length
                ? `messages=${verifyResult.messages.join(",")}`
                : "",
            verifyResult.hostname ? `hostname=${verifyResult.hostname}` : "",
            verifyResult.action ? `action=${verifyResult.action}` : "",
        ]
            .filter(Boolean)
            .join(" | ")
            .slice(0, 500);
        return jsonResponse(
            {
                ok: false,
                error: "Turnstile verification failed.",
                details: details || undefined,
            },
            400,
        );
    }

    if (!env.RESEND_API_KEY) {
        return jsonResponse(
            { ok: false, error: "Email provider not configured." },
            500,
        );
    }

    const fromEmail = env.CONTACT_FROM_EMAIL;
    if (!fromEmail) {
        return jsonResponse(
            { ok: false, error: "Sender email not configured." },
            500,
        );
    }

    const toEmail = env.CONTACT_TO_EMAIL ?? "calum@muir.in";
    const applicantName = [
        formatValue(fields.get("participant_first_names")),
        formatValue(fields.get("participant_last_name")),
    ]
        .filter(Boolean)
        .join(" ");

    const bodyText = sectionOrder
        .map(({ title, fields: sectionFields }) => {
            const lines = sectionFields
                .map((field) => {
                    const value = formatValue(fields.get(field));
                    if (!value) return null;
                    return `${labelMap[field] ?? toTitleCase(field)}: ${value}`;
                })
                .filter(Boolean)
                .join("\n");

            return lines ? `${title}\n${lines}` : "";
        })
        .filter(Boolean)
        .join("\n\n");

    const extraFields = [...fields.keys()].filter(
        (field) =>
            field !== "cf-turnstile-response" &&
            !sectionOrder.some((section) => section.fields.includes(field)),
    );
    const extraText = extraFields
        .map((field) => {
            const value = formatValue(fields.get(field));
            return value ? `${labelMap[field] ?? toTitleCase(field)}: ${value}` : "";
        })
        .filter(Boolean)
        .join("\n");

    const emailPayload = {
        from: fromEmail,
        to: [toEmail],
        reply_to: formatValue(fields.get("email")) || undefined,
        subject: `New participant application${applicantName ? `: ${applicantName}` : ""}`,
        text: [bodyText, extraText ? `Additional Fields\n${extraText}` : ""]
            .filter(Boolean)
            .join("\n\n"),
    };

    const response = await fetchWithTimeout(
        "https://api.resend.com/emails",
        {
            method: "POST",
            headers: {
                Authorization: `Bearer ${env.RESEND_API_KEY}`,
                "Content-Type": "application/json",
            },
            body: JSON.stringify(emailPayload),
        },
        8000,
        "Resend",
    );
    if (!response.ok) {
        const contentType = response.headers.get("content-type") ?? "";
        const errorText = contentType.includes("application/json")
            ? JSON.stringify(await response.json().catch(() => ({})))
            : await response.text();
        const errorCode = response.headers.get("x-ms-error-code");
        const details = [
            `${response.status} ${response.statusText}`.trim(),
            errorCode ? `code=${errorCode}` : "",
            errorText.trim(),
        ]
            .filter(Boolean)
            .join(" | ")
            .slice(0, 500);
        return jsonResponse(
            {
                ok: false,
                error: "Failed to send email.",
                details,
            },
            502,
        );
    }

    return jsonResponse({ ok: true });
};

export const onRequest: PagesFunction<Env> = async (context) => {
    try {
        if (context.request.method !== "POST") {
            return jsonResponse(
                { ok: false, error: "Method not allowed." },
                405,
            );
        }
        return await handlePost(context);
    } catch (error) {
        const message =
            error instanceof Error ? error.message : "Unknown error";
        return jsonResponse(
            { ok: false, error: "Unhandled exception.", details: message },
            500,
        );
    }
};
