<?php

/**
 * LolaCore — Hub Memory Reconcile Service
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Hub;

use LolaCore\Actions\ActionLogger;
use LolaCore\Memory\MemoryManager;
use WP_Error;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Batch memory reconcile receiver (push) and owner-memory export (pull) for LolaHub C4.
 */
final class HubMemoryReconcileService
{
    public const MAX_BATCH_SIZE = 200;

    /**
     * Default pull types — operational memory only (E10 §2 Q1: no business_context by default).
     *
     * @var string[]
     */
    public const DEFAULT_PULL_TYPES = [
        'site_state',
        'user_preference',
        'decision_history',
        'action_result',
        'disagreement',
        'plugin_conflict',
        'performance_metric',
    ];

    public function __construct(
        private ?MemoryManager $memory = null,
        private ?HubMemoryTransparency $transparency = null,
        private ?PairingRegistrar $pairing = null,
    ) {
        $this->memory       = $memory ?? new MemoryManager();
        $this->transparency = $transparency ?? new HubMemoryTransparency();
        $this->pairing      = $pairing ?? new PairingRegistrar();
    }

    /**
     * Push reconcile batch from master → child.
     *
     * @param array<string, mixed> $input
     * @return array<string, mixed>|WP_Error
     */
    public function receive(array $input): array|WP_Error
    {
        if (! empty($input['dry_run'])) {
            $facts = is_array($input['facts'] ?? null) ? $input['facts'] : [];

            return [
                'preview'  => true,
                'received' => count($facts),
                'message'  => __('Memory reconcile batch received for preview only.', 'lolacore'),
            ];
        }

        $facts = is_array($input['facts'] ?? null) ? $input['facts'] : [];
        if ($facts === []) {
            return new WP_Error(
                'lolacore_hub_reconcile_empty',
                __('At least one fact is required for memory reconcile.', 'lolacore'),
                ['status' => 400]
            );
        }

        if (count($facts) > self::MAX_BATCH_SIZE) {
            return new WP_Error(
                'lolacore_hub_reconcile_batch_too_large',
                sprintf(
                    /* translators: %d: maximum batch size */
                    __('Memory reconcile batches are limited to %d facts.', 'lolacore'),
                    self::MAX_BATCH_SIZE
                ),
                ['status' => 400]
            );
        }

        $inserted           = 0;
        $skippedDuplicates  = 0;
        $skippedCollisions  = 0;
        $updatedImportance  = 0;

        foreach ($facts as $fact) {
            if (! is_array($fact)) {
                ++$skippedDuplicates;
                continue;
            }

            $outcome = $this->memory->upsertHubFact($fact);
            match ($outcome) {
                'inserted'            => ++$inserted,
                'skipped_duplicate' => ++$skippedDuplicates,
                'skipped_collision' => ++$skippedCollisions,
                'updated_importance'=> ++$updatedImportance,
                default             => ++$skippedDuplicates,
            };
        }

        $watermark = $this->memory->getHubMemoryWatermark();
        $received  = count($facts);

        $this->transparency->recordReconcile([
            'synced_at'    => current_time('mysql'),
            'fact_count'   => $inserted + $updatedImportance,
            'master_label' => $this->pairing->getMasterLabel(),
        ]);

        $this->logReconcile($received, $inserted, $skippedDuplicates + $skippedCollisions, $watermark);

        return [
            'received'           => $received,
            'inserted'           => $inserted,
            'skipped_duplicates' => $skippedDuplicates + $skippedCollisions,
            'updated_importance' => $updatedImportance,
            'watermark'          => $watermark,
        ];
    }

    /**
     * Pull owner memory for master seed/adoption.
     *
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    public function readForPull(array $input = []): array
    {
        $types = $this->resolvePullTypes($input);
        $limit = max(1, min(500, (int) ($input['limit'] ?? 100)));

        $facts     = $this->memory->exportOwnerFactsForHub($types, $limit);
        $watermark = $this->memory->getOwnerMemoryWatermark($types);

        return [
            'facts'     => $facts,
            'watermark' => $watermark,
            'types'     => $types,
        ];
    }

    /**
     * @param array<string, mixed> $input
     * @return string[]
     */
    private function resolvePullTypes(array $input): array
    {
        $requested = $input['types'] ?? null;
        if (! is_array($requested) || $requested === []) {
            return self::DEFAULT_PULL_TYPES;
        }

        $types = array_values(array_filter(array_map('strval', $requested)));

        $includeBusiness = ! empty($input['include_business_context']);
        if ($includeBusiness && ! in_array('business_context', $types, true)) {
            $types[] = 'business_context';
        }

        if (! $includeBusiness) {
            $types = array_values(array_filter(
                $types,
                static fn(string $type) => $type !== 'business_context'
            ));
        }

        return $types !== [] ? $types : self::DEFAULT_PULL_TYPES;
    }

    /**
     * @param array<string, mixed> $watermark
     */
    private function logReconcile(int $received, int $inserted, int $skipped, array $watermark): void
    {
        $label  = $this->pairing->getMasterLabel();
        $nodeId = $this->pairing->getNodeId();
        $source = $label !== '' ? 'hub:' . $label : 'hub:' . $nodeId;

        $logger = new ActionLogger();
        $logger->log(
            'hub_memory_reconcile',
            sprintf(
                'Fleet operator synchronized %1$d memory facts (%2$d inserted, %3$d skipped).',
                $received,
                $inserted,
                $skipped
            ),
            'success',
            $source,
            [
                'received'  => $received,
                'inserted'  => $inserted,
                'skipped'   => $skipped,
                'watermark' => $watermark,
            ]
        );
    }
}
