import logging from '@tryghost/logging';
import EmailAddressParser, {EmailAddress} from './email-address-parser.js';

export type EmailAddresses = {
    from: EmailAddress,
    replyTo?: EmailAddress
}

export type EmailAddressesValidation = {
    allowed: boolean,
    verificationEmailRequired: boolean,
    reason?: string
}

export type EmailAddressType = 'from' | 'replyTo';

type GetAddressOptions = {
    useFallbackAddress: boolean
}

export class EmailAddressService {
    #getManagedEmailEnabled: () => boolean;
    #getSendingDomain: () => string | null;
    #getDefaultEmail: () => EmailAddress;
    #getFallbackDomain: () => string | null;
    #getFallbackEmail: () => EmailAddress | null;
    #getMembersSupportAddress: () => string;
    #isValidEmailAddress: (email: string) => boolean;

    constructor(dependencies: {
        getManagedEmailEnabled: () => boolean,
        getSendingDomain: () => string | null,
        getFallbackDomain: () => string | null,
        getDefaultEmail: () => EmailAddress,
        getFallbackEmail: () => string | null,
        getMembersSupportAddress: () => string,
        isValidEmailAddress: (email: string) => boolean
    }) {
        this.#getManagedEmailEnabled = dependencies.getManagedEmailEnabled;
        this.#getSendingDomain = dependencies.getSendingDomain;
        this.#getFallbackDomain = dependencies.getFallbackDomain;
        this.#getDefaultEmail = dependencies.getDefaultEmail;
        this.#getMembersSupportAddress = dependencies.getMembersSupportAddress;
        this.#getFallbackEmail = () => {
            const fallbackAddress = dependencies.getFallbackEmail();
            if (!fallbackAddress) {
                return null;
            }
            return EmailAddressParser.parse(fallbackAddress);
        };
        this.#isValidEmailAddress = dependencies.isValidEmailAddress;
    }

    get sendingDomain(): string | null {
        return this.#getSendingDomain();
    }

    get fallbackDomain(): string | null {
        return this.#getFallbackDomain();
    }

    get managedEmailEnabled(): boolean {
        return this.#getManagedEmailEnabled();
    }

    get defaultFromEmail(): EmailAddress {
        return this.#getDefaultEmail();
    }

    get fallbackEmail(): EmailAddress | null {
        return this.#getFallbackEmail();
    }

    /**
     * Get the actual sender address for member emails after DMARC transformation.
     * On managed platforms, this may differ from the configured support address to ensure DMARC compliance.
     */
    getMembersSupportAddress(): string {
        const configuredAddress = this.#getMembersSupportAddress();
        const transformed = this.getAddressFromString(configuredAddress);
        return transformed.from.address;
    }

    getAddressFromString(from: string, replyTo?: string): EmailAddresses {
        const parsedFrom = EmailAddressParser.parse(from);
        const parsedReplyTo = replyTo ? EmailAddressParser.parse(replyTo) : undefined;

        return this.getAddress({
            from: parsedFrom ?? this.defaultFromEmail,
            replyTo: parsedReplyTo ?? undefined
        });
    }

    /**
     * When sending an email, we should always ensure DMARC alignment.
     * Because of that, we restrict which email addresses we send from. All emails should be either
     * send from a configured domain (hostSettings.managedEmail.sendingDomains), or from the configured email address (mail.from).
     *
     * If we send an email from an email address that doesn't pass, we'll just default to the default email address,
     * and instead add a replyTo email address from the requested from address.
     */
    getAddress(preferred: EmailAddresses, options: GetAddressOptions = {useFallbackAddress: false}): EmailAddresses {
        if (preferred.replyTo && !this.#isValidEmailAddress(preferred.replyTo.address)) {
            // The default from-address itself is allowed to fail generic email validation
            // (e.g. noreply@127.0.0.1 on an IP-only install — see the same carve-out in
            // validate() below), so don't error-log when the replyTo just happens to match
            // it (case-insensitively — members_support_address is user-entered, so a case
            // variant of the same address shouldn't defeat the carve-out). This also
            // silences a genuinely malformed mail:from, not just the known IP-domain case —
            // demote to warn instead of dropping the signal entirely.
            if (preferred.replyTo.address.toLowerCase() !== this.defaultFromEmail.address.toLowerCase()) {
                logging.error(`[EmailAddresses] Invalid replyTo address: ${preferred.replyTo.address}`);
            } else {
                logging.warn(`[EmailAddresses] replyTo address ${preferred.replyTo.address} matches the default from-address and failed generic validation; removing it`);
            }
            // Remove invalid replyTo addresses
            preferred.replyTo = undefined;
        }

        // Validate the from address
        if (!this.#isValidEmailAddress(preferred.from.address)) {
            // Never allow an invalid email address
            return {
                from: this.defaultFromEmail,
                replyTo: preferred.replyTo || undefined
            };
        }

        if (!this.managedEmailEnabled) {
            // Self hoster or legacy Ghost Pro
            return preferred;
        }

        // Case: use fallback address when warming up custom domain
        if (options.useFallbackAddress) {
            const fallbackEmail = this.fallbackEmail;
            if (fallbackEmail) {
                if (!fallbackEmail.name) {
                    fallbackEmail.name = preferred.from.name || this.defaultFromEmail.name;
                }

                return {
                    from: fallbackEmail,
                    replyTo: preferred.replyTo || preferred.from || this.defaultFromEmail
                };
            } else {
                logging.error('[EmailAddresses] Fallback email address is not configured, cannot use fallback address for sending email.');
            }
        }

        // Case: always allow the default from address
        if (preferred.from.address === this.defaultFromEmail.address) {
            if (!preferred.from.name) {
                // Use the default sender name if it is missing
                preferred.from.name = this.defaultFromEmail.name;
            }

            return preferred;
        }

        if (this.sendingDomain) {
            // Check if FROM address is from the sending domain
            if (preferred.from.address.endsWith(`@${this.sendingDomain}`)) {
                return preferred;
            }

            // Not an error: this is the expected, handled fallback for any sender address that
            // doesn't match the sending domain (staff/member/newsletter senders routinely don't) —
            // we fall through to the default from-address below. Warn-level, not error-level.
            logging.warn(`[EmailAddresses] Cannot send emails from ${preferred.from.address} when sending domain is ${this.sendingDomain} — falling back to the default from-address`);
        }

        // Only allow to send from the configured from address
        const address = {
            from: this.defaultFromEmail,
            replyTo: preferred.replyTo || preferred.from
        };

        // Do allow to change the sender name if requested
        if (preferred.from.name) {
            address.from.name = preferred.from.name;
        }

        if (address.replyTo.address === address.from.address) {
            return {
                from: address.from
            };
        }

        return address;
    }

    /**
     * When changing any from or reply to addresses in the system, we need to validate them
     */
    validate(email: string, type: EmailAddressType): EmailAddressesValidation {
        if (!this.#isValidEmailAddress(email)) {
            // Never allow an invalid email address
            return {
                allowed: email === this.defaultFromEmail.address, // Localhost email noreply@127.0.0.1 is marked as invalid, but we should allow it
                verificationEmailRequired: false,
                reason: 'invalid'
            };
        }

        if (!this.managedEmailEnabled) {
            // Self hoster or legacy Ghost Pro
            return {
                allowed: true,
                verificationEmailRequired: false // Self hosters don't need to verify email addresses
            };
        }

        if (this.sendingDomain) {
            // Only allow it if it ends with the sending domain
            if (email.endsWith(`@${this.sendingDomain}`)) {
                return {
                    allowed: true,
                    verificationEmailRequired: false
                };
            }

            // Use same restrictions as one without a sending domain for other addresses
        }

        // Only allow to edit the replyTo address, with verification
        if (type === 'replyTo') {
            return {
                allowed: true,
                verificationEmailRequired: email !== this.defaultFromEmail.address
            };
        }

        // Not allowed to change from
        return {
            allowed: email === this.defaultFromEmail.address,
            verificationEmailRequired: false,
            reason: 'not allowed'
        };
    }
}
