<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * @return string
 */
function elementsell_get_email_blacklist_table_name() {
    global $wpdb;

    return $wpdb->prefix . 'elementsell_email_blacklist';
}

/**
 * @param string $email
 * @return string
 */
function elementsell_normalize_mailing_email( $email ) {
    return strtolower( sanitize_email( (string) $email ) );
}

/**
 * @return string
 */
function elementsell_get_unsubscribe_secret() {
    return (string) wp_salt( 'auth' );
}

/**
 * @param string $email
 * @return string
 */
function elementsell_generate_unsubscribe_token( $email ) {
    $email = elementsell_normalize_mailing_email( $email );

    if ( $email === '' ) {
        return '';
    }

    return hash_hmac( 'sha256', $email, elementsell_get_unsubscribe_secret() );
}

/**
 * @param string $email
 * @param string $token
 * @return bool
 */
function elementsell_verify_unsubscribe_token( $email, $token ) {
    $email = elementsell_normalize_mailing_email( $email );
    $token = (string) $token;

    if ( $email === '' || $token === '' ) {
        return false;
    }

    return hash_equals( elementsell_generate_unsubscribe_token( $email ), $token );
}

/**
 * @param string $email
 * @return string
 */
function elementsell_get_unsubscribe_url_for_email( $email ) {
    $email = elementsell_normalize_mailing_email( $email );

    if ( $email === '' ) {
        return elementsell_get_unsubscribe_page_url();
    }

    return add_query_arg(
        [
            'email' => rawurlencode( $email ),
            'token' => elementsell_generate_unsubscribe_token( $email ),
        ],
        elementsell_get_unsubscribe_page_url()
    );
}

/**
 * @return string
 */
function elementsell_get_unsubscribe_link_template() {
    $base = elementsell_get_unsubscribe_page_url();

    return add_query_arg(
        [
            'email' => '{{email}}',
            'token' => '{{token}}',
        ],
        $base
    );
}

/**
 * @param string $body
 * @param string $email
 * @return string
 */
function elementsell_append_unsubscribe_footer_to_email_body( $body, $email ) {
    $body  = trim( (string) $body );
    $email = elementsell_normalize_mailing_email( $email );

    if ( $email === '' ) {
        return $body;
    }

    $url = elementsell_get_unsubscribe_url_for_email( $email );

    if ( $url === '' ) {
        return $body;
    }

    if ( stripos( $body, 'odhlásit' ) !== false || stripos( $body, 'odhlasit' ) !== false || strpos( $body, $url ) !== false ) {
        return $body;
    }

    $footer = '<p style="margin:18px 0 0;font-size:12px;line-height:1.5;color:#6b7280;text-align:center;">'
        . '<a href="' . esc_url( $url ) . '" style="color:#6b7280;text-decoration:underline;">Odhlásit odběr e-mailů</a>'
        . '</p>';

    if ( $body === '' ) {
        return $footer;
    }

    return $body . "\n\n" . $footer;
}

/**
 * @param string $email
 * @param string $reason manual|unsubscribe
 * @return true|WP_Error
 */
function elementsell_add_email_to_blacklist( $email, $reason = 'manual' ) {
    global $wpdb;

    $email  = elementsell_normalize_mailing_email( $email );
    $reason = in_array( (string) $reason, [ 'manual', 'unsubscribe' ], true ) ? (string) $reason : 'manual';

    if ( $email === '' || ! is_email( $email ) ) {
        return new WP_Error( 'elementsell_invalid_email', 'Zadejte platný e-mail.' );
    }

    $table = elementsell_get_email_blacklist_table_name();

    $existing = $wpdb->get_var(
        $wpdb->prepare(
            "SELECT id FROM {$table} WHERE email = %s LIMIT 1",
            $email
        )
    );

    if ( $existing ) {
        $wpdb->update(
            $table,
            [
                'reason'     => $reason,
                'created_at' => current_time( 'mysql', true ),
            ],
            [ 'id' => (int) $existing ],
            [ '%s', '%s' ],
            [ '%d' ]
        );

        return true;
    }

    $inserted = $wpdb->insert(
        $table,
        [
            'email'      => $email,
            'reason'     => $reason,
            'created_at' => current_time( 'mysql', true ),
        ],
        [ '%s', '%s', '%s' ]
    );

    if ( ! $inserted ) {
        return new WP_Error( 'elementsell_blacklist_insert', 'E-mail se nepodařilo přidat na blacklist.' );
    }

    return true;
}

/**
 * @param string $email
 * @return bool
 */
function elementsell_remove_email_from_blacklist( $email ) {
    global $wpdb;

    $email = elementsell_normalize_mailing_email( $email );

    if ( $email === '' ) {
        return false;
    }

    return (bool) $wpdb->delete(
        elementsell_get_email_blacklist_table_name(),
        [ 'email' => $email ],
        [ '%s' ]
    );
}

/**
 * @param string $email
 * @return bool
 */
function elementsell_is_email_blacklisted( $email ) {
    global $wpdb;

    $email = elementsell_normalize_mailing_email( $email );

    if ( $email === '' ) {
        return false;
    }

    $table = elementsell_get_email_blacklist_table_name();

    if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
        return false;
    }

    return (bool) $wpdb->get_var(
        $wpdb->prepare(
            "SELECT 1 FROM {$table} WHERE email = %s LIMIT 1",
            $email
        )
    );
}

/**
 * @return array<int, object>
 */
function elementsell_get_blacklisted_emails() {
    global $wpdb;

    $table = elementsell_get_email_blacklist_table_name();

    if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
        return [];
    }

    return $wpdb->get_results(
        "SELECT id, email, reason, created_at
         FROM {$table}
         ORDER BY created_at DESC, id DESC"
    );
}

/**
 * @return array<string, string>
 */
function elementsell_get_mailing_list_role_options() {
    $options = [];

    if ( ! function_exists( 'wp_roles' ) || ! function_exists( 'elementsell_get_protected_role_keys' ) ) {
        return $options;
    }

    $protected = elementsell_get_protected_role_keys();

    foreach ( wp_roles()->roles as $role_key => $role_data ) {
        if ( in_array( $role_key, $protected, true ) ) {
            continue;
        }

        $options[ $role_key ] = translate_user_role( $role_data['name'] ) . ' (' . $role_key . ')';
    }

    return $options;
}

/**
 * @return string[]
 */
function elementsell_get_mailing_list_product_options() {
    global $wpdb;

    $products = [];
    $tables   = [ $wpdb->prefix . 'elementsell_orders' ];

    if ( function_exists( 'elementsell_orders_archive_table_exists' ) && elementsell_orders_archive_table_exists() ) {
        $tables[] = elementsell_get_orders_archive_table_name();
    }

    foreach ( $tables as $table ) {
        $rows = $wpdb->get_col(
            "SELECT DISTINCT product_name
             FROM {$table}
             WHERE product_name IS NOT NULL
             AND product_name != ''
             ORDER BY product_name ASC"
        );

        foreach ( (array) $rows as $product_name ) {
            $product_name = trim( (string) $product_name );

            if ( $product_name !== '' ) {
                $products[ $product_name ] = $product_name;
            }
        }
    }

    return array_values( $products );
}

/**
 * @param array $args
 * @return array{
 *     status_paid: bool,
 *     status_unpaid: bool,
 *     roles: string[],
 *     products: string[],
 *     date_field: string,
 *     date_from: string,
 *     date_to: string,
 *     exclude_blacklist: bool
 * }
 */
function elementsell_parse_mailing_list_filters( $args = [] ) {
    $args = is_array( $args ) ? $args : [];

    $status_paid   = array_key_exists( 'status_paid', $args ) ? ! empty( $args['status_paid'] ) : true;
    $status_unpaid = array_key_exists( 'status_unpaid', $args ) ? ! empty( $args['status_unpaid'] ) : true;

    if ( ! $status_paid && ! $status_unpaid ) {
        $status_paid   = true;
        $status_unpaid = true;
    }

    $roles = [];

    if ( ! empty( $args['roles'] ) && is_array( $args['roles'] ) ) {
        $roles = array_values(
            array_filter(
                array_map( 'sanitize_key', wp_unslash( $args['roles'] ) )
            )
        );
    }

    $products = [];

    if ( ! empty( $args['products'] ) && is_array( $args['products'] ) ) {
        $products = array_values(
            array_filter(
                array_map(
                    static function ( $value ) {
                        return sanitize_text_field( wp_unslash( (string) $value ) );
                    },
                    $args['products']
                )
            )
        );
    }

    $date_field = isset( $args['date_field'] ) && (string) $args['date_field'] === 'paid_at' ? 'paid_at' : 'created_at';
    $date_from  = isset( $args['date_from'] ) ? sanitize_text_field( wp_unslash( (string) $args['date_from'] ) ) : '';
    $date_to    = isset( $args['date_to'] ) ? sanitize_text_field( wp_unslash( (string) $args['date_to'] ) ) : '';

    return [
        'status_paid'       => $status_paid,
        'status_unpaid'     => $status_unpaid,
        'roles'             => $roles,
        'products'          => $products,
        'date_field'        => $date_field,
        'date_from'         => $date_from,
        'date_to'           => $date_to,
        'exclude_blacklist' => ! array_key_exists( 'exclude_blacklist', $args ) || ! empty( $args['exclude_blacklist'] ),
    ];
}

/**
 * @param string $fields_table
 * @return string
 */
function elementsell_get_mailing_orders_exclusion_sql( $fields_table = '' ) {
    global $wpdb;

    if ( $fields_table === '' ) {
        $fields_table = $wpdb->prefix . 'elementsell_order_fields';
    }

    return " AND o.email != ''
             AND o.email IS NOT NULL
             AND o.id NOT IN (
                 SELECT order_id
                 FROM {$fields_table}
                 WHERE field_name = 'environment'
                 AND field_value = 'demo'
             )";
}

/**
 * @param array  $filters
 * @param string $orders_table
 * @param string $fields_table
 * @return array{sql: string, params: array<int, mixed>}
 */
function elementsell_build_mailing_orders_where_clause( $filters, $orders_table, $fields_table ) {
    $sql    = elementsell_get_mailing_orders_exclusion_sql( $fields_table );
    $params = [];

    $statuses = [];

    if ( ! empty( $filters['status_paid'] ) ) {
        $statuses[] = 'paid';
    }

    if ( ! empty( $filters['status_unpaid'] ) && function_exists( 'elementsell_get_unpaid_payment_statuses' ) ) {
        $statuses = array_merge( $statuses, elementsell_get_unpaid_payment_statuses() );
    }

    $statuses = array_values( array_unique( $statuses ) );

    if ( ! empty( $statuses ) ) {
        $placeholders = implode( ', ', array_fill( 0, count( $statuses ), '%s' ) );
        $sql         .= " AND o.payment_status IN ({$placeholders})";
        $params       = array_merge( $params, $statuses );
    }

    if ( ! empty( $filters['products'] ) ) {
        $placeholders = implode( ', ', array_fill( 0, count( $filters['products'] ), '%s' ) );
        $sql         .= " AND o.product_name IN ({$placeholders})";
        $params       = array_merge( $params, $filters['products'] );
    }

    $date_field = ! empty( $filters['date_field'] ) && $filters['date_field'] === 'paid_at' ? 'paid_at' : 'created_at';

    if ( ! empty( $filters['date_from'] ) ) {
        $sql     .= " AND o.{$date_field} IS NOT NULL AND o.{$date_field} != '' AND DATE(o.{$date_field}) >= %s";
        $params[] = $filters['date_from'];
    }

    if ( ! empty( $filters['date_to'] ) ) {
        $sql     .= " AND o.{$date_field} IS NOT NULL AND o.{$date_field} != '' AND DATE(o.{$date_field}) <= %s";
        $params[] = $filters['date_to'];
    }

    return [
        'sql'    => $sql,
        'params' => $params,
    ];
}

/**
 * @param array $filters
 * @return array<int, object>
 */
function elementsell_get_mailing_list_orders( $filters ) {
    global $wpdb;

    $filters      = elementsell_parse_mailing_list_filters( $filters );
    $orders_table = $wpdb->prefix . 'elementsell_orders';
    $fields_table = $wpdb->prefix . 'elementsell_order_fields';
    $queries      = [];
    $all_params   = [];

    $where = elementsell_build_mailing_orders_where_clause( $filters, $orders_table, $fields_table );

    $queries[]  = "SELECT o.id, o.email, o.first_name, o.surname, o.phone, o.product_name, o.payment_status, o.created_at, o.paid_at
                   FROM {$orders_table} o
                   WHERE 1=1 {$where['sql']}";
    $all_params = array_merge( $all_params, $where['params'] );

    if ( function_exists( 'elementsell_orders_archive_table_exists' ) && elementsell_orders_archive_table_exists() ) {
        $archive_table   = elementsell_get_orders_archive_table_name();
        $archive_fields  = elementsell_get_order_fields_archive_table_name();
        $archive_where   = elementsell_build_mailing_orders_where_clause( $filters, $archive_table, $archive_fields );

        $queries[]  = "SELECT o.id, o.email, o.first_name, o.surname, o.phone, o.product_name, o.payment_status, o.created_at, o.paid_at
                       FROM {$archive_table} o
                       WHERE 1=1 {$archive_where['sql']}";
        $all_params = array_merge( $all_params, $archive_where['params'] );
    }

    $sql = implode( ' UNION ALL ', $queries ) . ' ORDER BY created_at DESC';

    if ( empty( $all_params ) ) {
        return $wpdb->get_results( $sql );
    }

    return $wpdb->get_results( $wpdb->prepare( $sql, $all_params ) );
}

/**
 * @param string $product_name
 * @return string
 */
function elementsell_get_mailing_role_key_for_product( $product_name ) {
    if ( ! function_exists( 'elementsell_sanitize_product_role_key' ) ) {
        return '';
    }

    return elementsell_sanitize_product_role_key( (string) $product_name );
}

/**
 * @param string $email
 * @return string[]
 */
function elementsell_get_mailing_user_role_keys( $email ) {
    $user = get_user_by( 'email', elementsell_normalize_mailing_email( $email ) );

    if ( ! $user ) {
        return [];
    }

    $roles = array_values( array_map( 'strval', (array) $user->roles ) );

    if ( ! function_exists( 'elementsell_get_protected_role_keys' ) ) {
        return $roles;
    }

    return array_values(
        array_diff( $roles, elementsell_get_protected_role_keys() )
    );
}

/**
 * @param int $order_id
 * @return string
 */
function elementsell_get_mailing_order_address( $order_id ) {
    if ( ! function_exists( 'elementsell_get_order_field' ) ) {
        return '';
    }

    return trim( (string) elementsell_get_order_field( (int) $order_id, 'address' ) );
}

/**
 * @param array<int, object> $orders
 * @param array              $filters
 * @return array<int, array<string, mixed>>
 */
function elementsell_aggregate_mailing_contacts( $orders, $filters = [] ) {
    $filters  = elementsell_parse_mailing_list_filters( $filters );
    $contacts = [];

    foreach ( (array) $orders as $order ) {
        $email = elementsell_normalize_mailing_email( $order->email ?? '' );

        if ( $email === '' ) {
            continue;
        }

        if ( ! isset( $contacts[ $email ] ) ) {
            $contacts[ $email ] = [
                'email'              => $email,
                'name'               => '',
                'phone'              => '',
                'address'            => '',
                'roles'             => [],
                'paid_roles'        => [],
                'unpaid_roles'      => [],
                'unpaid_role_dates' => [],
                'paid_role_dates'   => [],
                'products'          => [],
                'has_paid'           => false,
                'has_unpaid'         => false,
                'last_order_at'      => '',
                'last_order_id'      => 0,
                'order_count'        => 0,
            ];
        }

        $contact =& $contacts[ $email ];
        ++$contact['order_count'];

        $is_paid = (string) $order->payment_status === 'paid';

        if ( $is_paid ) {
            $contact['has_paid'] = true;
        } elseif ( function_exists( 'elementsell_order_is_unpaid' ) && elementsell_order_is_unpaid( $order ) ) {
            $contact['has_unpaid'] = true;
        } else {
            $contact['has_unpaid'] = true;
        }

        $product_name = trim( (string) $order->product_name );
        $created_at   = (string) $order->created_at;

        if ( $product_name !== '' ) {
            $contact['products'][ $product_name ] = $product_name;

            $role_key = elementsell_get_mailing_role_key_for_product( $product_name );

            if ( $role_key !== '' ) {
                $contact['roles'][ $role_key ] = $role_key;

                if ( $is_paid ) {
                    $contact['paid_roles'][ $role_key ] = $role_key;

                    if ( $created_at !== '' && ( empty( $contact['paid_role_dates'][ $role_key ] ) || $created_at > $contact['paid_role_dates'][ $role_key ] ) ) {
                        $contact['paid_role_dates'][ $role_key ] = $created_at;
                    }
                } elseif ( function_exists( 'elementsell_order_is_unpaid' ) && elementsell_order_is_unpaid( $order ) ) {
                    $contact['unpaid_roles'][ $role_key ] = $role_key;

                    if ( $created_at !== '' && ( empty( $contact['unpaid_role_dates'][ $role_key ] ) || $created_at > $contact['unpaid_role_dates'][ $role_key ] ) ) {
                        $contact['unpaid_role_dates'][ $role_key ] = $created_at;
                    }
                }
            }
        }

        if ( $created_at !== '' && ( $contact['last_order_at'] === '' || $created_at > $contact['last_order_at'] ) ) {
            $contact['last_order_at'] = $created_at;
            $contact['last_order_id'] = (int) $order->id;

            $name = trim( (string) $order->first_name . ' ' . (string) $order->surname );

            if ( $name !== '' ) {
                $contact['name'] = $name;
            }

            $phone = trim( (string) $order->phone );

            if ( $phone !== '' ) {
                $contact['phone'] = $phone;
            }
        }
    }

    foreach ( $contacts as $email => $contact ) {
        foreach ( elementsell_get_mailing_user_role_keys( $email ) as $role_key ) {
            $contact['roles'][ $role_key ]       = $role_key;
            $contact['paid_roles'][ $role_key ]  = $role_key;
        }

        if ( $contact['last_order_id'] > 0 ) {
            $address = elementsell_get_mailing_order_address( $contact['last_order_id'] );

            if ( $address !== '' ) {
                $contact['address'] = $address;
            }
        }

        if ( $contact['address'] === '' && $contact['last_order_id'] > 0 ) {
            $contact['address'] = elementsell_get_mailing_order_address( $contact['last_order_id'] );
        }

        $contacts[ $email ] = $contact;
    }

    $result = [];

    foreach ( $contacts as $email => $contact ) {
        if ( ! empty( $filters['exclude_blacklist'] ) && elementsell_is_email_blacklisted( $email ) ) {
            continue;
        }

        if ( ! empty( $filters['roles'] ) ) {
            $role_keys = array_values( $contact['roles'] );
            $matched   = array_intersect( $role_keys, $filters['roles'] );

            if ( empty( $matched ) ) {
                continue;
            }
        }

        if ( ! empty( $filters['status_paid'] ) && empty( $filters['status_unpaid'] ) && empty( $contact['has_paid'] ) ) {
            continue;
        }

        if ( ! empty( $filters['status_unpaid'] ) && empty( $filters['status_paid'] ) && empty( $contact['has_unpaid'] ) ) {
            continue;
        }

        $contact['roles']            = array_values( $contact['roles'] );
        $contact['paid_roles']       = array_values( $contact['paid_roles'] );
        $contact['unpaid_roles']     = array_values( $contact['unpaid_roles'] );
        $contact['products']         = array_values( $contact['products'] );
        $contact['status']   = elementsell_format_mailing_contact_status( $contact );

        $result[] = $contact;
    }

    usort(
        $result,
        static function ( $a, $b ) {
            return strcmp( (string) $b['last_order_at'], (string) $a['last_order_at'] );
        }
    );

    return $result;
}

/**
 * @param array $contact
 * @return string
 */
function elementsell_format_mailing_contact_status( $contact ) {
    $has_paid   = ! empty( $contact['has_paid'] );
    $has_unpaid = ! empty( $contact['has_unpaid'] );

    if ( $has_paid && $has_unpaid ) {
        return 'zaplaceno i nezaplaceno';
    }

    if ( $has_paid ) {
        return 'zaplaceno';
    }

    if ( $has_unpaid ) {
        return 'nezaplaceno';
    }

    return '—';
}

/**
 * @param array $filters
 * @return array{contacts: array<int, array<string, mixed>>, total: int}
 */
function elementsell_query_mailing_contacts( $filters = [] ) {
    $orders   = elementsell_get_mailing_list_orders( $filters );
    $contacts = elementsell_aggregate_mailing_contacts( $orders, $filters );

    return [
        'contacts' => $contacts,
        'total'    => count( $contacts ),
    ];
}

/**
 * @param array $args
 * @return array{
 *     email: bool,
 *     name: bool,
 *     phone: bool,
 *     address: bool,
 *     roles: bool,
 *     product: bool,
 *     last_order: bool,
 *     order_count: bool
 * }
 */
function elementsell_parse_mailing_export_columns( $args = [] ) {
    $defaults = [
        'email'       => true,
        'name'        => true,
        'phone'       => true,
        'address'     => true,
        'roles'       => true,
        'product'     => true,
        'last_order'  => true,
        'order_count' => true,
    ];

    $args = is_array( $args ) ? $args : [];

    foreach ( $defaults as $key => $default ) {
        if ( ! array_key_exists( $key, $args ) ) {
            $args[ $key ] = $default;
        } else {
            $args[ $key ] = ! empty( $args[ $key ] );
        }
    }

    return $args;
}

/**
 * @param array<int, array<string, mixed>> $contacts
 * @param array                              $columns
 * @return string
 */
function elementsell_build_mailing_contacts_csv( $contacts, $columns ) {
    $columns = elementsell_parse_mailing_export_columns( $columns );

    $headers = [];
    $keys    = [];

    $map = [
        'email'       => 'E-mail',
        'name'        => 'Jméno',
        'phone'       => 'Telefon',
        'address'     => 'Adresa',
        'roles'       => 'Role',
        'product'     => 'Produkt',
        'last_order'  => 'Datum poslední objednávky',
        'order_count' => 'Počet objednávek',
    ];

    foreach ( $map as $key => $label ) {
        if ( ! empty( $columns[ $key ] ) ) {
            $headers[] = $label;
            $keys[]    = $key;
        }
    }

    $handle = fopen( 'php://temp', 'r+' );

    if ( ! $handle ) {
        return '';
    }

    fputcsv( $handle, $headers, ';' );

    foreach ( (array) $contacts as $contact ) {
        $row = [];

        foreach ( $keys as $key ) {
            switch ( $key ) {
                case 'email':
                    $row[] = (string) ( $contact['email'] ?? '' );
                    break;
                case 'name':
                    $row[] = (string) ( $contact['name'] ?? '' );
                    break;
                case 'phone':
                    $row[] = (string) ( $contact['phone'] ?? '' );
                    break;
                case 'address':
                    $row[] = (string) ( $contact['address'] ?? '' );
                    break;
                case 'roles':
                    $row[] = implode( ', ', (array) ( $contact['roles'] ?? [] ) );
                    break;
                case 'product':
                    $row[] = implode( ', ', (array) ( $contact['products'] ?? [] ) );
                    break;
                case 'last_order':
                    $value = (string) ( $contact['last_order_at'] ?? '' );
                    $row[] = $value !== '' ? mysql2date( 'Y-m-d H:i', $value, false ) : '';
                    break;
                case 'order_count':
                    $row[] = (string) (int) ( $contact['order_count'] ?? 0 );
                    break;
                default:
                    $row[] = '';
            }
        }

        fputcsv( $handle, $row, ';' );
    }

    rewind( $handle );
    $csv = stream_get_contents( $handle );
    fclose( $handle );

    return "\xEF\xBB\xBF" . (string) $csv;
}
