<?php
/**
 * Email Notifications Handler.
 *
 * Handles sending email notifications for call events by delegating
 * to the SiteStaffr middleware (SendGrid) instead of wp_mail().
 *
 * @link       https://github.com/sitestaffr/sitestaffr-plugin
 * @since      1.0.0
 *
 * @package    SiteStaffr
 * @subpackage SiteStaffr/includes
 */

/**
 * Email Notifications Handler class.
 *
 * Provides methods for sending email notifications when calls complete.
 * Email rendering and delivery is handled by the middleware via SendGrid.
 * This class acts as a data provider — it fetches call data from the DB,
 * builds a structured payload, and sends it to the middleware.
 *
 * @since      1.0.0
 * @package    SiteStaffr
 * @subpackage SiteStaffr/includes
 * @author     SiteStaffr
 */
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Plugin table names are internally derived from $wpdb->prefix and values are placeholder-prepared.
class SiteStaffr_Email_Notifications {

    /**
     * Last mail error message for diagnostic display.
     *
     * @since 0.14.269
     * @var string
     */
    private $last_mail_error = '';

    /**
     * Get the last mail error message.
     *
     * @since 0.14.269
     * @return string
     */
    public function get_last_mail_error() {
        return $this->last_mail_error;
    }

    /**
     * Send notification email for a completed call via middleware.
     *
     * Fetches call data from the database, builds a structured
     * payload, and sends it to the middleware for rendering and delivery via SendGrid.
     *
     * This method is triggered by the sitestaffr_call_completed action which fires
     * when a call ends with any terminal status (completed, no-answer, failed, busy, canceled).
     *
     * @since    1.0.0
     * @param    int       $call_id      The call ID to send notification for.
     * @param    string    $call_status  Optional. The final call status.
     * @return   bool                    True if email sent successfully, false otherwise.
     */
    public function send_call_notification( $call_id, $call_status = '' ) {
        SiteStaffr_Logger::legacy( 'SiteStaffr Email: send_call_notification called for call_id: ' . $call_id . ', status: ' . $call_status );

        // Get settings
        $settings = get_option( 'sitestaffr_settings', array() );

        // Check if notifications are enabled
        if ( isset( $settings['email_notifications_enabled'] ) && ! $settings['email_notifications_enabled'] ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                SiteStaffr_Logger::legacy( 'SiteStaffr Email: Notifications are disabled in settings' );
            }
            return false;
        }

        // Get recipient email
        $to = ! empty( $settings['notification_email'] ) ? $settings['notification_email'] : get_option( 'admin_email' );

        if ( empty( $to ) || ! is_email( $to ) ) {
            SiteStaffr_Logger::legacy( 'SiteStaffr Email: No valid notification email configured' );
            return false;
        }
        $cc_recipients = $this->get_additional_notification_recipients( $settings, $to );

        // Get call data
        $call = $this->get_call_data( $call_id );
        if ( ! $call ) {
            SiteStaffr_Logger::legacy( 'SiteStaffr Email: Cannot send notification - call not found: ' . $call_id );
            return false;
        }

        // Skip filtered calls (is_billable = 0) - these are spam/low-quality calls
        if ( isset( $call->is_billable ) && $call->is_billable == 0 ) {
            SiteStaffr_Logger::legacy( "SiteStaffr Email: Call {$call_id} filtered (is_billable=0) - email notification skipped" );
            return false;
        }

        SiteStaffr_Logger::legacy( "SiteStaffr Email: Sending notification for billable call {$call_id}" );

        // Build structured email data for middleware
        $email_data = array(
            'business_name'  => ! empty( $settings['business_name'] ) ? stripslashes( $settings['business_name'] ) : 'SiteStaffr',
            'call_started_at' => $this->format_started_at_eastern( $call->started_at ),
            'call_duration_seconds' => ! empty( $call->call_duration ) ? (int) $call->call_duration : 0,
            'call_status'    => $call->call_status,
            'is_test_call'   => ! empty( $call->is_test_call ),
            'summary_html'   => ! empty( $call->ai_summary ) ? stripslashes( $call->ai_summary ) : '',
            'dashboard_url'  => admin_url( 'admin.php?page=sitestaffr' ),
            'call_detail_url' => $this->get_call_url( $call_id ),
            'settings_url'   => admin_url( 'admin.php?page=sitestaffr-settings' ),
            'plan_inactive'  => ( class_exists( 'SiteStaffr_Billing_State' ) && SiteStaffr_Billing_State::is_subscription_inactive() ),
        );

        // Send one notification request with optional CC recipients.
        $result = $this->send_via_middleware(
            SiteStaffr_Public_Middleware_Client::path_send_notification_email(),
            $to,
            $email_data,
            $cc_recipients
        );

        $recipient_log = $to;
        if ( ! empty( $cc_recipients ) ) {
            $recipient_log .= sprintf(
                /* translators: %d: Number of CC recipients */
                __( ' (+%d CC)', 'sitestaffr' ),
                count( $cc_recipients )
            );
        }
        $this->log_email_result( $call_id, $recipient_log, $result );

        return $result;
    }

    /**
     * Send test email to verify settings work via middleware.
     *
     * @since    1.0.0
     * @param    string|null    $to    Optional email address to send to.
     * @return   bool                  True if email sent successfully, false otherwise.
     */
    public function send_test_email( $to = null ) {
        $this->last_mail_error = '';
        $settings = get_option( 'sitestaffr_settings', array() );

        $cc_recipients = array();
        if ( ! $to ) {
            $to = ! empty( $settings['notification_email'] ) ? $settings['notification_email'] : get_option( 'admin_email' );
            $cc_recipients = $this->get_additional_notification_recipients( $settings, $to );
        }

        if ( empty( $to ) || ! is_email( $to ) ) {
            $this->last_mail_error = 'Invalid recipient email address.';
            SiteStaffr_Logger::legacy( 'SiteStaffr Email: Cannot send test email - invalid recipient' );
            return false;
        }

        $email_data = array(
            'business_name'      => ! empty( $settings['business_name'] ) ? stripslashes( $settings['business_name'] ) : 'Your Business',
            'notification_email' => ! empty( $settings['notification_email'] ) ? $settings['notification_email'] : '',
            'dashboard_url'      => admin_url( 'admin.php?page=sitestaffr' ),
            'settings_url'       => admin_url( 'admin.php?page=sitestaffr-settings' ),
            'current_utc'        => gmdate( 'c' ),
        );

        $result = $this->send_via_middleware(
            SiteStaffr_Public_Middleware_Client::path_send_test_email(),
            $to,
            $email_data,
            $cc_recipients
        );

        $recipient_log = $to;
        if ( ! empty( $cc_recipients ) ) {
            $recipient_log .= ' +' . count( $cc_recipients ) . ' CC';
        }

        if ( $result ) {
            SiteStaffr_Logger::legacy( 'SiteStaffr Email: Test email sent successfully to ' . $recipient_log );
        } else {
            SiteStaffr_Logger::legacy( 'SiteStaffr Email: Test email FAILED to send to ' . $recipient_log . ( $this->last_mail_error ? ' — ' . $this->last_mail_error : '' ) );
        }

        return $result;
    }

    /**
     * Send email via middleware (SendGrid).
     *
     * Makes an HMAC-signed POST request to the middleware email endpoint.
     *
     * @since    0.14.269
     * @param    string    $path        Middleware API path.
     * @param    string    $to          Recipient email address.
     * @param    array     $email_data  Structured email data.
     * @param    array     $cc_recipients Optional CC recipients array.
     * @return   bool                   True if middleware accepted the request.
     */
    private function send_via_middleware( $path, $to, $email_data, $cc_recipients = array() ) {
        $api_secret = $this->get_api_secret();
        $preflight  = SiteStaffr_Public_Middleware_Client::get_public_preflight_config( $api_secret );

        if ( $preflight['missing_api_secret'] || $preflight['missing_middleware_url'] ) {
            $this->last_mail_error = 'Email service not configured. Please complete plugin setup.';
            SiteStaffr_Logger::legacy( 'SiteStaffr Email: Missing API secret or middleware URL' );
            return false;
        }

        $site_token = get_option( 'sitestaffr_site_token', '' );

        $payload = array(
            'site_token' => $site_token,
            'site_url'   => site_url(),
            'to'         => $to,
            'email_data' => $email_data,
        );
        $normalized_cc = $this->normalize_cc_recipients( $cc_recipients, $to );
        if ( ! empty( $normalized_cc ) ) {
            // Include CC at both top-level and in email_data for middleware compatibility.
            $payload['cc'] = $normalized_cc;
            $payload['email_data']['cc'] = $normalized_cc;
        }

        $payload_json = wp_json_encode( $payload );
        $endpoint     = SiteStaffr_Public_Middleware_Client::build_middleware_endpoint(
            $preflight['middleware_url'],
            $path
        );

        $response = SiteStaffr_Public_Middleware_Client::post_signed_json_with_json_response(
            $endpoint,
            $payload_json,
            $api_secret,
            null,
            '',
            SiteStaffr_Public_Middleware_Client::timeout_email(),
            SiteStaffr_Public_Middleware_Client::extra_headers_json()
        );

        if ( is_wp_error( $response ) ) {
            $this->last_mail_error = 'Failed to connect to email service: ' . $response->get_error_message();
            return false;
        }

        $status_code  = isset( $response['status_code'] ) ? (int) $response['status_code'] : 0;
        $decoded_body = isset( $response['decoded_body'] ) ? $response['decoded_body'] : null;

        if ( 200 === $status_code && ! empty( $decoded_body['success'] ) ) {
            return true;
        }

        // Extract error detail from middleware response
        $error_detail = '';
        if ( is_array( $decoded_body ) ) {
            if ( ! empty( $decoded_body['detail'] ) ) {
                $error_detail = $decoded_body['detail'];
            } elseif ( ! empty( $decoded_body['error'] ) ) {
                $error_detail = $decoded_body['error'];
            }
        }

        $this->last_mail_error = ! empty( $error_detail )
            ? 'Email service error: ' . $error_detail
            : 'Email service returned status ' . $status_code;

        SiteStaffr_Logger::legacy(
            sprintf(
                'SiteStaffr Email: Middleware returned %s',
                SiteStaffr_Public_Middleware_Client::get_status_body_for_log(
                    $status_code,
                    isset( $response['body'] ) ? $response['body'] : ''
                )
            )
        );

        return false;
    }

    /**
     * Get the API secret for HMAC signing.
     *
     * @since    0.14.269
     * @return   string
     */
    private function get_api_secret() {
        // Prefer per-site secret (v0.13.72+), fall back to global secret
        $site_api_secret = get_option( 'sitestaffr_site_api_secret' );
        if ( ! empty( $site_api_secret ) ) {
            return $site_api_secret;
        }

        $api_secret = get_option( 'sitestaffr_api_secret' );
        if ( empty( $api_secret ) ) {
            $api_secret = bin2hex( random_bytes( 32 ) );
            update_option( 'sitestaffr_api_secret', $api_secret );
        }

        return $api_secret;
    }

    /**
     * Get additional notification recipients from settings.
     *
     * @since 0.14.269
     * @param array  $settings      Plugin settings.
     * @param string $primary_email Primary recipient to exclude from CC list.
     * @return array
     */
    private function get_additional_notification_recipients( $settings, $primary_email = '' ) {
        $max_cc_recipients = 5;
        if ( empty( $settings['notification_cc_emails'] ) || ! is_string( $settings['notification_cc_emails'] ) ) {
            return array();
        }

        $emails = $this->parse_notification_email_list( $settings['notification_cc_emails'] );
        if ( empty( $emails ) ) {
            return array();
        }

        if ( empty( $primary_email ) ) {
            return array_slice( $emails, 0, $max_cc_recipients );
        }

        $primary_normalized = strtolower( sanitize_email( $primary_email ) );
        $filtered_emails = array_values(
            array_filter(
                $emails,
                function ( $email ) use ( $primary_normalized ) {
                    return strtolower( $email ) !== $primary_normalized;
                }
            )
        );
        return array_slice( $filtered_emails, 0, $max_cc_recipients );
    }

    /**
     * Parse comma/semicolon/newline-separated email list.
     *
     * @since 0.14.269
     * @param string $raw_emails Raw email list string.
     * @return array
     */
    private function parse_notification_email_list( $raw_emails ) {
        if ( ! is_string( $raw_emails ) || '' === trim( $raw_emails ) ) {
            return array();
        }

        $parts = preg_split( '/[,;\r\n]+/', $raw_emails );
        if ( false === $parts || empty( $parts ) ) {
            return array();
        }

        $emails = array();
        $seen   = array();

        foreach ( $parts as $part ) {
            $candidate = trim( (string) $part );
            if ( '' === $candidate ) {
                continue;
            }

            $email = sanitize_email( $candidate );
            if ( empty( $email ) || ! is_email( $email ) ) {
                continue;
            }

            $key = strtolower( $email );
            if ( isset( $seen[ $key ] ) ) {
                continue;
            }

            $seen[ $key ] = true;
            $emails[] = $email;
        }

        return $emails;
    }

    /**
     * Normalize CC recipient list to unique valid emails and exclude primary recipient.
     *
     * @since 0.14.269
     * @param array  $cc_recipients Raw CC recipients.
     * @param string $primary_email Primary recipient email.
     * @return array
     */
    private function normalize_cc_recipients( $cc_recipients, $primary_email = '' ) {
        if ( ! is_array( $cc_recipients ) || empty( $cc_recipients ) ) {
            return array();
        }

        $normalized = array();
        $seen = array();
        $primary_normalized = strtolower( sanitize_email( $primary_email ) );

        foreach ( $cc_recipients as $cc_recipient ) {
            $email = sanitize_email( (string) $cc_recipient );
            if ( empty( $email ) || ! is_email( $email ) ) {
                continue;
            }

            $email_key = strtolower( $email );
            if ( '' !== $primary_normalized && $email_key === $primary_normalized ) {
                continue;
            }
            if ( isset( $seen[ $email_key ] ) ) {
                continue;
            }

            $seen[ $email_key ] = true;
            $normalized[] = $email;
        }

        return $normalized;
    }

    /**
     * Get call data from database.
     *
     * @since    1.0.0
     * @param    int    $call_id    The call ID to retrieve.
     * @return   object|null        Call object or null if not found.
     */
    private function get_call_data( $call_id ) {
        global $wpdb;

        $table = $wpdb->prefix . 'phoneease_calls';

        return $wpdb->get_row(
            $wpdb->prepare(
                "SELECT * FROM $table WHERE id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin table name.
                $call_id
            )
        );
    }

    /**
     * Log email sending result.
     *
     * @since    1.0.0
     * @param    int       $call_id    Call ID the notification was for.
     * @param    string    $to         Recipient email address.
     * @param    bool      $sent       Whether email was sent successfully.
     */
    private function log_email_result( $call_id, $to, $sent ) {
        $status = $sent ? 'SUCCESS' : 'FAILED';
        SiteStaffr_Logger::legacy( sprintf( 'SiteStaffr Email: %s - Call #%d notification to %s', $status, $call_id, $to ) );
    }

    /**
     * Format phone number for display.
     *
     * @since    1.0.0
     * @param    string    $number    Phone number to format.
     * @return   string               Formatted phone number.
     */
    public function format_phone_number( $number ) {
        if ( class_exists( 'SiteStaffr_Call_Manager' ) && method_exists( 'SiteStaffr_Call_Manager', 'format_phone_number' ) ) {
            return SiteStaffr_Call_Manager::format_phone_number( $number );
        }

        $clean = preg_replace( '/[^0-9]/', '', $number );

        if ( strlen( $clean ) === 11 && substr( $clean, 0, 1 ) === '1' ) {
            $area_code = substr( $clean, 1, 3 );
            $prefix = substr( $clean, 4, 3 );
            $line = substr( $clean, 7, 4 );
            return "($area_code) $prefix-$line";
        }

        if ( strlen( $clean ) === 10 ) {
            $area_code = substr( $clean, 0, 3 );
            $prefix = substr( $clean, 3, 3 );
            $line = substr( $clean, 6, 4 );
            return "($area_code) $prefix-$line";
        }

        return $number;
    }

    /**
     * Format call duration for display.
     *
     * @since    1.0.0
     * @param    int    $seconds    Duration in seconds.
     * @return   string             Formatted duration string.
     */
    public function format_duration( $seconds ) {
        if ( empty( $seconds ) || $seconds <= 0 ) {
            return __( '0 seconds', 'sitestaffr' );
        }

        $minutes = floor( $seconds / 60 );
        $remaining_seconds = $seconds % 60;

        if ( $minutes > 0 ) {
            return sprintf(
                /* translators: 1: Number of minutes, 2: Number of seconds */
                __( '%1$d min %2$d sec', 'sitestaffr' ),
                $minutes,
                $remaining_seconds
            );
        }

        return sprintf(
            /* translators: %d: Number of seconds */
            __( '%d seconds', 'sitestaffr' ),
            $seconds
        );
    }

    /**
     * Format datetime for display.
     *
     * @since    1.0.0
     * @param    string    $datetime    MySQL datetime string.
     * @return   string                 Formatted datetime string.
     */
    public function format_datetime( $datetime ) {
        if ( empty( $datetime ) ) {
            return __( 'Unknown', 'sitestaffr' );
        }

        $timestamp = strtotime( $datetime );
        $date_format = get_option( 'date_format', 'F j, Y' );
        $time_format = get_option( 'time_format', 'g:i a' );

        return date_i18n( $date_format . ' ' . $time_format, $timestamp );
    }

    /**
     * Get dashboard URL for a specific call.
     *
     * @since    1.0.0
     * @param    int    $call_id    The call ID.
     * @return   string             URL to view call in dashboard.
     */
    public function get_call_url( $call_id ) {
        return SiteStaffr_Magic_Link::generate_url( $call_id );
    }

    /**
     * Format started_at timestamp in Eastern Time as ISO 8601.
     *
     * @since  1.2.0
     * @param  mixed $started_at Unix timestamp (seconds or milliseconds) or empty.
     * @return string             ISO 8601 string in Eastern Time, or empty string.
     */
    private function format_started_at_eastern( $started_at ) {
        if ( empty( $started_at ) ) {
            return '';
        }

        $ts = (int) $started_at;
        if ( $ts > 9999999999 ) {
            $ts = (int) floor( $ts / 1000 );
        }

        if ( $ts <= 0 ) {
            return '';
        }

        try {
            $dt = new DateTime( '@' . $ts );
            $dt->setTimezone( new DateTimeZone( 'America/New_York' ) );
            return $dt->format( 'c' );
        } catch ( Exception $e ) {
            return '';
        }
    }

    /**
     * Get human-readable status label.
     *
     * @since    1.0.0
     * @param    string    $status    Call status from database.
     * @return   string               Human-readable status label.
     */
    private function get_status_label( $status ) {
        $labels = array(
            'completed'  => __( 'Completed', 'sitestaffr' ),
            'no-answer'  => __( 'Missed', 'sitestaffr' ),
            'busy'       => __( 'Busy', 'sitestaffr' ),
            'failed'     => __( 'Failed', 'sitestaffr' ),
            'canceled'   => __( 'Canceled', 'sitestaffr' ),
            'in-progress' => __( 'In Progress', 'sitestaffr' ),
            'ringing'    => __( 'Ringing', 'sitestaffr' ),
            'queued'     => __( 'Queued', 'sitestaffr' ),
        );

        return isset( $labels[ $status ] ) ? $labels[ $status ] : __( 'New', 'sitestaffr' );
    }
}
// phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
