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

/**
 * Persistence layer: per-post analysis meta + site-level reports / prompt history.
 * All post meta uses the _aivt_ prefix so Lifecycle purge can remove it cleanly.
 */
class AIVT_Store {

    const META_GEO       = '_aivt_geo';        // ['score'=>int,'breakdown'=>[],'tips'=>[],'ts'=>int]
    const META_GEO_HIST  = '_aivt_geo_history'; // [['score'=>int,'ts'=>int], ...] capped
    const META_ENTITIES  = '_aivt_entities';
    const META_EEAT      = '_aivt_eeat';
    const META_CITE      = '_aivt_citability';
    const META_FAQ       = '_aivt_faq';
    const META_QA        = '_aivt_qa';
    const META_SCHEMA    = '_aivt_schema';

    const OPT_PROMPT_HISTORY  = 'aivt_prompt_history';
    const OPT_COMPETITORS      = 'aivt_competitor_report';
    const OPT_GEO_SNAPSHOTS    = 'aivt_geo_snapshots'; // site avg score over time
    const OPT_APPROVALS        = 'aivt_approvals';     // pending FAQ/Q&A awaiting review/insert

    /* ---- Per-post ---- */

    public static function save($post_id, $meta_key, $data) {
        $data = is_array($data) ? $data : ['value' => $data];
        $data['ts'] = time();
        update_post_meta((int) $post_id, $meta_key, wp_slash($data));
        if ($meta_key === self::META_GEO && isset($data['score'])) {
            self::push_geo_history((int) $post_id, (int) $data['score']);
        }
        return $data;
    }

    /** Append a GEO score point to per-post history (Pro trend feature). */
    private static function push_geo_history($post_id, $score) {
        $hist = get_post_meta($post_id, self::META_GEO_HIST, true);
        if (!is_array($hist)) {
            $hist = [];
        }
        $hist[] = ['score' => (int) $score, 'ts' => time()];
        $hist = array_slice($hist, -30);
        update_post_meta($post_id, self::META_GEO_HIST, $hist);
    }

    /** Per-post GEO history points (oldest → newest). */
    public static function geo_history($post_id, $limit = 30) {
        $hist = get_post_meta((int) $post_id, self::META_GEO_HIST, true);
        if (!is_array($hist)) {
            return [];
        }
        return array_slice($hist, -(int) $limit);
    }

    public static function get($post_id, $meta_key) {
        $val = get_post_meta((int) $post_id, $meta_key, true);
        return is_array($val) ? $val : null;
    }

    public static function geo_score($post_id) {
        $g = self::get($post_id, self::META_GEO);
        return $g && isset($g['score']) ? (int) $g['score'] : null;
    }

    /* ---- Site-level GEO stats ---- */

    public static function site_geo_stats() {
        $post_types = aivt_enabled_post_types();
        if (empty($post_types)) {
            $post_types = ['post', 'page'];
        }
        $q = new WP_Query([
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => 500,
            'fields'         => 'ids',
            'no_found_rows'  => false,
            'meta_key'       => self::META_GEO, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Admin-only dashboard aggregate.
        ]);
        $scores = [];
        foreach ($q->posts as $pid) {
            $s = self::geo_score($pid);
            if ($s !== null) {
                $scores[] = $s;
            }
        }
        $analyzed = count($scores);
        $avg = $analyzed ? (int) round(array_sum($scores) / $analyzed) : 0;
        $below = count(array_filter($scores, function ($v) { return $v < 70; }));

        $total = new WP_Query([
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => 1,
            'fields'         => 'ids',
        ]);
        return [
            'avg_score' => $avg,
            'analyzed'  => $analyzed,
            'below_70'  => $below,
            'total'     => (int) $total->found_posts,
        ];
    }

    /** Published posts of enabled types that have no GEO analysis yet. */
    public static function unanalyzed_ids($limit = 100) {
        $post_types = aivt_enabled_post_types();
        if (empty($post_types)) {
            $post_types = ['post', 'page'];
        }
        $q = new WP_Query([
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => (int) $limit,
            'fields'         => 'ids',
            'orderby'        => 'modified',
            'order'          => 'DESC',
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin-only coverage query, run on demand, not on a public hot path.
            'meta_query'     => [
                ['key' => self::META_GEO, 'compare' => 'NOT EXISTS'],
            ],
        ]);
        return array_map('intval', $q->posts);
    }

    /** Published posts of enabled types missing a given analysis/generation meta. */
    public static function ids_missing_meta($meta_key, $limit = 1000) {
        $post_types = aivt_enabled_post_types();
        if (empty($post_types)) {
            $post_types = ['post', 'page'];
        }
        $q = new WP_Query([
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => (int) $limit,
            'fields'         => 'ids',
            'orderby'        => 'modified',
            'order'          => 'DESC',
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin-only bulk-job selection, run on demand.
            'meta_query'     => [
                ['key' => (string) $meta_key, 'compare' => 'NOT EXISTS'],
            ],
        ]);
        return array_map('intval', $q->posts);
    }

    public static function count_unanalyzed() {
        $post_types = aivt_enabled_post_types();
        if (empty($post_types)) {
            $post_types = ['post', 'page'];
        }
        $q = new WP_Query([
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => 1,
            'fields'         => 'ids',
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin-only coverage count, run on demand, not on a public hot path.
            'meta_query'     => [
                ['key' => self::META_GEO, 'compare' => 'NOT EXISTS'],
            ],
        ]);
        return (int) $q->found_posts;
    }

    /* ---- Prompt-test history (Pro) ---- */

    public static function add_prompt_run($entry) {
        $history = get_option(self::OPT_PROMPT_HISTORY, []);
        if (!is_array($history)) {
            $history = [];
        }
        $entry['ts'] = time();
        array_unshift($history, $entry);
        $history = array_slice($history, 0, 100);
        update_option(self::OPT_PROMPT_HISTORY, $history, false);
        return $entry;
    }

    public static function prompt_history($limit = 50) {
        $history = get_option(self::OPT_PROMPT_HISTORY, []);
        if (!is_array($history)) {
            return [];
        }
        return array_slice($history, 0, (int) $limit);
    }

    /* ---- Competitor report (Pro/Agency) ---- */

    public static function save_competitor_report($data) {
        $data['ts'] = time();
        update_option(self::OPT_COMPETITORS, $data, false);
        return $data;
    }

    public static function competitor_report() {
        $r = get_option(self::OPT_COMPETITORS, null);
        return is_array($r) ? $r : null;
    }

    /* ---- Site GEO snapshots (trend) ---- */

    /** Record today's site avg GEO score once per day (idempotent within a day). */
    public static function maybe_snapshot_site($avg = null) {
        $snaps = get_option(self::OPT_GEO_SNAPSHOTS, []);
        if (!is_array($snaps)) {
            $snaps = [];
        }
        $today = gmdate('Y-m-d');
        $last = end($snaps);
        if (is_array($last) && ($last['date'] ?? '') === $today) {
            return $snaps; // already captured today
        }
        if ($avg === null) {
            $stats = self::site_geo_stats();
            $avg = (int) $stats['avg_score'];
        }
        $snaps[] = ['date' => $today, 'avg' => (int) $avg, 'ts' => time()];
        $snaps = array_slice($snaps, -60);
        update_option(self::OPT_GEO_SNAPSHOTS, $snaps, false);
        return $snaps;
    }

    public static function site_geo_snapshots($limit = 30) {
        $snaps = get_option(self::OPT_GEO_SNAPSHOTS, []);
        if (!is_array($snaps)) {
            return [];
        }
        return array_slice($snaps, -(int) $limit);
    }

    /* ---- Prompt trend (brand presence over time) ---- */

    /**
     * Brand-mention percentage per day from prompt history (oldest → newest).
     * Returns [['date'=>'Y-m-d','pct'=>int,'runs'=>int], ...].
     */
    public static function prompt_trend($days = 30) {
        $history = self::prompt_history(100);
        if (empty($history)) {
            return [];
        }
        $buckets = [];
        foreach ($history as $h) {
            $day = gmdate('Y-m-d', (int) ($h['ts'] ?? time()));
            if (!isset($buckets[$day])) {
                $buckets[$day] = ['hits' => 0, 'runs' => 0];
            }
            $buckets[$day]['runs']++;
            if (!empty($h['mentioned'])) {
                $buckets[$day]['hits']++;
            }
        }
        ksort($buckets);
        $out = [];
        foreach ($buckets as $day => $b) {
            $out[] = [
                'date' => $day,
                'pct'  => $b['runs'] ? (int) round(($b['hits'] / $b['runs']) * 100) : 0,
                'runs' => $b['runs'],
            ];
        }
        return array_slice($out, -(int) $days);
    }

    /* ---- Approval queue (bulk FAQ/Q&A → review → insert) ---- */

    /** Queue generated blocks for human review before they touch post content. */
    public static function add_approval($post_id, $kind, $blocks, $count = 0) {
        $blocks = (string) $blocks;
        if ($blocks === '') {
            return null;
        }
        $items = get_option(self::OPT_APPROVALS, []);
        if (!is_array($items)) {
            $items = [];
        }
        // Replace any existing pending item of the same kind for this post.
        $items = array_values(array_filter($items, function ($i) use ($post_id, $kind) {
            return !((int) ($i['post_id'] ?? 0) === (int) $post_id && ($i['kind'] ?? '') === $kind);
        }));
        $entry = [
            'id'      => uniqid('aivt_', false),
            'post_id' => (int) $post_id,
            'kind'    => (string) $kind,
            'blocks'  => $blocks,
            'count'   => (int) $count,
            'ts'      => time(),
        ];
        array_unshift($items, $entry);
        $items = array_slice($items, 0, 300);
        update_option(self::OPT_APPROVALS, $items, false);
        return $entry;
    }

    public static function get_approvals($limit = 100) {
        $items = get_option(self::OPT_APPROVALS, []);
        if (!is_array($items)) {
            return [];
        }
        return array_slice($items, 0, (int) $limit);
    }

    public static function get_approval($id) {
        foreach (self::get_approvals(300) as $i) {
            if (($i['id'] ?? '') === $id) {
                return $i;
            }
        }
        return null;
    }

    public static function remove_approval($id) {
        $items = get_option(self::OPT_APPROVALS, []);
        if (!is_array($items)) {
            return;
        }
        $items = array_values(array_filter($items, function ($i) use ($id) {
            return ($i['id'] ?? '') !== $id;
        }));
        update_option(self::OPT_APPROVALS, $items, false);
    }

    public static function count_approvals() {
        return count(self::get_approvals(300));
    }

    /* ---- Site-wide report rows (CSV export) ---- */

    /** Rows for the GEO coverage report: every enabled published post. */
    public static function geo_report_rows($limit = 2000) {
        $post_types = aivt_enabled_post_types();
        if (empty($post_types)) {
            $post_types = ['post', 'page'];
        }
        $q = new WP_Query([
            'post_type'      => $post_types,
            'post_status'    => 'publish',
            'posts_per_page' => (int) $limit,
            'orderby'        => 'modified',
            'order'          => 'DESC',
            'no_found_rows'  => true,
        ]);
        $rows = [];
        foreach ($q->posts as $post) {
            $geo = self::get($post->ID, self::META_GEO);
            $score = ($geo && isset($geo['score'])) ? (int) $geo['score'] : null;
            $tip = '';
            if ($geo && !empty($geo['tips'][0])) {
                $tip = (string) $geo['tips'][0];
            }
            $rows[] = [
                'id'        => (int) $post->ID,
                'title'     => get_the_title($post),
                'type'      => $post->post_type,
                'url'       => get_permalink($post),
                'score'     => $score,
                'label'     => $score !== null ? AIVT_GEO_Score::score_label($score) : '',
                'analyzed'  => $score !== null ? 'yes' : 'no',
                'top_tip'   => $tip,
            ];
        }
        return $rows;
    }
}
