<?php
/**
 * apCampaign campaign intelligence.
 *
 * @package apCampaign
 */

declare(strict_types=1);

if (!defined('ABSPATH')) {
    exit;
}

if (!class_exists('APCampaign_Intelligence')) {
    final class APCampaign_Intelligence
    {
        /**
         * @param array<string,mixed> $campaign
         * @return array<string,mixed>
         */
        public static function score_campaign(array $campaign): array
        {
            $subject = trim((string) ($campaign['subject_line'] ?? ''));
            $preheader = trim((string) ($campaign['preheader_text'] ?? ''));
            $html = (string) ($campaign['html'] ?? $campaign['rendered_html'] ?? '');
            $audience = sanitize_text_field((string) ($campaign['audience_key'] ?? 'general'));
            $customer_history = self::customer_history_benchmark($audience);
            $spam = APCampaign_Spam_Score::score($subject, $html, $preheader);

            $subject_score = self::score_subject($subject);
            $content_score = self::score_content($html);
            $cta_score = self::score_cta($html);
            $overall_numeric = (int) round(($subject_score['numeric'] * 0.4) + ($content_score['numeric'] * 0.3) + ($cta_score['numeric'] * 0.2) + ($spam['score'] * 0.1));
            $overall = self::grade($overall_numeric);

            $base_open = $customer_history['has_history'] ? (float) $customer_history['avg_open_rate'] : 22.0;
            $base_click = $customer_history['has_history'] ? (float) $customer_history['avg_click_rate'] : 1.8;
            $predicted_open = max(8.0, min(38.0, $base_open + (($subject_score['numeric'] - 75) / 5)));
            $predicted_click = max(0.4, min(8.0, $base_click + (($cta_score['numeric'] - 75) / 35)));
            if ($spam['score'] < 70) {
                $predicted_open *= 0.85;
                $predicted_click *= 0.8;
            }

            $data_sources = [
                'built_in_campaign_intelligence_rules',
                'spam_deliverability_score',
            ];
            if ($customer_history['has_history']) {
                $data_sources[] = 'customer_owned_campaign_history';
            }

            return [
                'mode' => $customer_history['has_history'] ? 'customer_history_intelligence' : 'campaign_intelligence',
                'predicted_open_rate' => round($predicted_open, 2),
                'predicted_click_rate' => round($predicted_click, 2),
                'confidence' => $customer_history['has_history'] ? 'medium' : 'starter',
                'subject_score' => $subject_score['grade'],
                'content_score' => $content_score['grade'],
                'cta_score' => $cta_score['grade'],
                'overall_score' => $overall,
                'findings' => array_values(array_filter([
                    $subject_score['finding'],
                    $content_score['finding'],
                    $cta_score['finding'],
                    $spam['score'] < 70 ? 'Deliverability risk is high enough to require review before sending.' : '',
                ])),
                'suggestions' => array_values(array_filter(array_merge(
                    $subject_score['suggestions'],
                    $content_score['suggestions'],
                    $cta_score['suggestions'],
                    array_map(static fn(array $issue): string => (string) ($issue['fix'] ?? ''), (array) ($spam['issues'] ?? []))
                ))),
                'data_sources_used' => $data_sources,
                'private_data_used' => false,
                'spam_score' => $spam,
                'history_summary' => $customer_history,
            ];
        }

        /**
         * @return array<string,mixed>
         */
        private static function score_subject(string $subject): array
        {
            $numeric = 75;
            $suggestions = [];
            $finding = 'Subject line is serviceable.';

            if ($subject === '') {
                return ['numeric' => 40, 'grade' => 'F', 'finding' => 'Subject line is missing.', 'suggestions' => ['Add a specific subject line before testing or sending.']];
            }
            $length = strlen($subject);
            if ($length < 18) {
                $numeric -= 12;
                $suggestions[] = 'Add more context or a clearer value proposition to the subject line.';
            } elseif ($length > 60) {
                $numeric -= 8;
                $suggestions[] = 'Shorten the subject line so it survives inbox truncation.';
            } else {
                $numeric += 6;
            }

            if (preg_match('/\b(new|available|introducing|guide|report|exclusive|learn|discover|inside|today)\b/i', $subject)) {
                $numeric += 8;
                $finding = 'Subject line includes a recognizable value signal.';
            }
            if (preg_match('/test|asdf|lorem|hello world/i', $subject)) {
                $numeric -= 30;
                $finding = 'Subject line appears to contain test or placeholder language.';
                $suggestions[] = 'Replace test language with the actual advertiser, topic, offer, or editorial value.';
            }

            $numeric = max(0, min(100, $numeric));
            return ['numeric' => $numeric, 'grade' => self::grade($numeric), 'finding' => $finding, 'suggestions' => $suggestions];
        }

        /**
         * @return array<string,mixed>
         */
        private static function score_content(string $html): array
        {
            $plain = trim(wp_strip_all_tags($html));
            $numeric = 72;
            $suggestions = [];
            $finding = 'Content has enough structure for a basic campaign review.';

            if ($plain === '') {
                return ['numeric' => 35, 'grade' => 'F', 'finding' => 'Email body is empty or not readable as text.', 'suggestions' => ['Add real body copy or ensure the HTML renders meaningful content.']];
            }
            if (strlen($plain) < 120) {
                $numeric -= 12;
                $suggestions[] = 'Add enough readable copy to explain why the reader should act.';
            } else {
                $numeric += 8;
            }
            if (preg_match('/lorem ipsum|your headline here|placeholder|click here/i', $plain)) {
                $numeric -= 20;
                $finding = 'Content appears to contain placeholder language.';
                $suggestions[] = 'Remove placeholder content before approval.';
            }
            if (preg_match_all('/<img\b/i', $html) > 0 && strlen($plain) > 180) {
                $numeric += 5;
            }

            $numeric = max(0, min(100, $numeric));
            return ['numeric' => $numeric, 'grade' => self::grade($numeric), 'finding' => $finding, 'suggestions' => $suggestions];
        }

        /**
         * @return array<string,mixed>
         */
        private static function score_cta(string $html): array
        {
            $numeric = 70;
            $suggestions = [];
            $finding = 'CTA could be evaluated from the campaign links.';
            preg_match_all('/<a\s+[^>]*href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/is', $html, $matches, PREG_SET_ORDER);
            $valid = [];
            foreach ($matches as $match) {
                $text = trim(wp_strip_all_tags((string) $match[2]));
                $href = (string) $match[1];
                if ($text === '' || stripos($href, 'unsubscribe') !== false || stripos($text, 'unsubscribe') !== false) {
                    continue;
                }
                $valid[] = ['text' => $text, 'href' => $href];
            }

            if (!$valid) {
                return ['numeric' => 45, 'grade' => 'D', 'finding' => 'No clear call to action was found.', 'suggestions' => ['Add one clear call to action with a verified destination URL.']];
            }
            if (count($valid) <= 4) {
                $numeric += 10;
            } elseif (count($valid) > 12) {
                $numeric -= 10;
                $suggestions[] = 'Reduce competing calls to action or group related links.';
            }
            foreach ($valid as $cta) {
                if (preg_match('/^(click here|learn more|read more)$/i', $cta['text'])) {
                    $numeric -= 4;
                    $suggestions[] = 'Use more specific CTA text than "' . $cta['text'] . '".';
                    break;
                }
            }

            $numeric = max(0, min(100, $numeric));
            return ['numeric' => $numeric, 'grade' => self::grade($numeric), 'finding' => $finding, 'suggestions' => $suggestions];
        }

        /**
         * @return array<string,mixed>
         */
        private static function customer_history_benchmark(string $audience): array
        {
            global $wpdb;
            $t = APCampaign_Schema::tables();
            $table = $t['history_metrics'];
            $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
            if ($exists !== $table) {
                return ['has_history' => false, 'avg_open_rate' => 0, 'avg_click_rate' => 0, 'record_count' => 0];
            }

            $row = $wpdb->get_row($wpdb->prepare(
                "SELECT COUNT(*) AS count, AVG(open_rate) AS avg_open, AVG(click_rate) AS avg_click FROM {$table} WHERE audience_key = %s OR %s = ''",
                $audience,
                $audience
            ), ARRAY_A);
            $count = (int) ($row['count'] ?? 0);
            if ($count < 5) {
                return ['has_history' => false, 'avg_open_rate' => 0, 'avg_click_rate' => 0, 'record_count' => $count];
            }

            return [
                'has_history' => true,
                'avg_open_rate' => round((float) $row['avg_open'], 2),
                'avg_click_rate' => round((float) $row['avg_click'], 2),
                'record_count' => $count,
            ];
        }

        private static function grade(int $numeric): string
        {
            if ($numeric >= 90) {
                return 'A';
            }
            if ($numeric >= 82) {
                return 'B+';
            }
            if ($numeric >= 74) {
                return 'B';
            }
            if ($numeric >= 65) {
                return 'C';
            }
            if ($numeric >= 55) {
                return 'D';
            }
            return 'F';
        }
    }
}
