<?php

/**
 * LolaCore — Hub Agency Profile Service
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Hub;

use LolaCore\Actions\ActionLogger;
use LolaCore\Admin\Settings;
use LolaCore\Playbooks\Playbook;
use LolaCore\Playbooks\PlaybookLoader;
use LolaCore\Playbooks\PlaybookRegistry;
use LolaCore\Playbooks\PlaybookStore;
use LolaCore\Playbooks\PlaybookValidator;
use LolaCore\Skills\SkillDraftManager;
use LolaCore\Skills\YamlParser;
use LolaCore\Skills\SkillValidator;
use WP_Error;

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

/**
 * Accepts agency provisioning packages from the LolaHub master (sprint C3).
 *
 * Aditive by default: local owner playbooks/skills are never overwritten silently.
 */
final class HubAgencyProfileService
{
    public const SKILL_ORIGIN = 'hub_provisioned';

    public const PROVISIONED_PLAYBOOKS_KEY = 'hub_provisioned_playbooks';

    public const AGENCY_PREFERENCES_KEY = 'hub_agency_preferences';

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>|WP_Error
     */
    public function apply(array $input): array|WP_Error
    {
        if (! empty($input['dry_run'])) {
            return $this->buildPreview($input);
        }

        $playbooks   = is_array($input['playbooks'] ?? null) ? $input['playbooks'] : [];
        $skills      = is_array($input['skills'] ?? null) ? $input['skills'] : [];
        $preferences = is_array($input['preferences'] ?? null) ? $input['preferences'] : [];

        $result = [
            'applied'  => [
                'playbooks'   => [],
                'skills'      => [],
                'preferences' => [],
            ],
            'updated'  => [
                'playbooks' => [],
                'skills'    => [],
            ],
            'omitted'  => [
                'playbooks'   => [],
                'skills'      => [],
                'preferences' => [],
            ],
        ];

        foreach ($playbooks as $entry) {
            if (! is_array($entry)) {
                continue;
            }
            $this->applyPlaybookEntry($entry, $result);
        }

        foreach ($skills as $entry) {
            if (! is_array($entry)) {
                continue;
            }
            $this->applySkillEntry($entry, $result);
        }

        if ($preferences !== []) {
            $this->applyPreferences($preferences, $result);
        }

        $this->logProvisioning($result);

        return $result;
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    private function buildPreview(array $input): array
    {
        $playbooks   = is_array($input['playbooks'] ?? null) ? $input['playbooks'] : [];
        $skills      = is_array($input['skills'] ?? null) ? $input['skills'] : [];
        $preferences = is_array($input['preferences'] ?? null) ? $input['preferences'] : [];

        return [
            'preview'   => true,
            'playbooks' => count($playbooks),
            'skills'    => count($skills),
            'preferences' => array_values(array_filter(array_keys($preferences), 'is_string')),
            'message'   => __('Agency profile package received for preview only.', 'lolacore'),
        ];
    }

    /**
     * @param array<string, mixed> $entry
     * @param array<string, mixed> $result
     */
    private function applyPlaybookEntry(array $entry, array &$result): void
    {
        $slug    = $this->sanitizeSlug((string) ($entry['slug'] ?? ''));
        $content = (string) ($entry['content'] ?? '');

        if ($slug === '' || $content === '') {
            $result['omitted']['playbooks'][] = [
                'slug'   => $slug,
                'reason' => 'missing_slug_or_content',
            ];
            return;
        }

        $loader   = new PlaybookLoader(new YamlParser(), new PlaybookValidator());
        $registry = new PlaybookRegistry($loader);
        $existing = $registry->getBySlug($slug);
        $hubSlugs = $this->getProvisionedPlaybookSlugs();

        if ($existing !== null) {
            $origin = $existing->getOrigin();
            if ($origin !== Playbook::ORIGIN_DB) {
                $result['omitted']['playbooks'][] = [
                    'slug'   => $slug,
                    'reason' => 'local_playbook_exists',
                ];
                return;
            }

            if (! in_array($slug, $hubSlugs, true)) {
                $result['omitted']['playbooks'][] = [
                    'slug'   => $slug,
                    'reason' => 'owner_playbook_exists',
                ];
                return;
            }

            $store = new PlaybookStore($loader);
            $row   = $store->getRowBySlug($slug);
            if ($row === null) {
                $result['omitted']['playbooks'][] = [
                    'slug'   => $slug,
                    'reason' => 'playbook_row_missing',
                ];
                return;
            }

            try {
                $store->update((int) $row['id'], $content);
            } catch (\Throwable $e) {
                $result['omitted']['playbooks'][] = [
                    'slug'   => $slug,
                    'reason' => 'update_failed',
                ];
                return;
            }

            if (! empty($entry['activate'])) {
                $store->setStatus((int) $row['id'], Playbook::STATUS_ACTIVE);
            }

            $result['updated']['playbooks'][] = $slug;
            return;
        }

        $store = new PlaybookStore($loader);

        try {
            $id = $store->create($slug, $content);
        } catch (\Throwable $e) {
            $result['omitted']['playbooks'][] = [
                'slug'   => $slug,
                'reason' => 'create_failed',
            ];
            return;
        }

        if (! empty($entry['activate'])) {
            $store->setStatus($id, Playbook::STATUS_ACTIVE);
        }

        $this->markPlaybookProvisioned($slug);
        $result['applied']['playbooks'][] = $slug;
    }

    /**
     * @param array<string, mixed> $entry
     * @param array<string, mixed> $result
     */
    private function applySkillEntry(array $entry, array &$result): void
    {
        $skillName = sanitize_title((string) ($entry['skill_name'] ?? $entry['slug'] ?? ''));
        $content   = (string) ($entry['content'] ?? '');

        if ($skillName === '' || $content === '') {
            $result['omitted']['skills'][] = [
                'skill_name' => $skillName,
                'reason'     => 'missing_name_or_content',
            ];
            return;
        }

        $draftManager = new SkillDraftManager(new YamlParser(), new SkillValidator());
        $existing     = $draftManager->getBySkillName($skillName);

        if ($existing !== null) {
            $origin = (string) ($existing['origin'] ?? '');
            if ($origin !== self::SKILL_ORIGIN) {
                $result['omitted']['skills'][] = [
                    'skill_name' => $skillName,
                    'reason'     => 'local_skill_exists',
                ];
                return;
            }

            try {
                $draftManager->update((int) $existing['id'], $content);
            } catch (\Throwable $e) {
                $result['omitted']['skills'][] = [
                    'skill_name' => $skillName,
                    'reason'     => 'update_failed',
                ];
                return;
            }

            if (! empty($entry['activate'])) {
                $draftManager->activate((int) $existing['id']);
            }

            $result['updated']['skills'][] = $skillName;
            return;
        }

        try {
            $id = $draftManager->insert($skillName, $content, self::SKILL_ORIGIN);
        } catch (\Throwable $e) {
            $result['omitted']['skills'][] = [
                'skill_name' => $skillName,
                'reason'     => 'create_failed',
            ];
            return;
        }

        if (! empty($entry['activate'])) {
            $draftManager->activate($id);
        }

        $result['applied']['skills'][] = $skillName;
    }

    /**
     * @param array<string, mixed> $preferences
     * @param array<string, mixed> $result
     */
    private function applyPreferences(array $preferences, array &$result): void
    {
        $allowedKeys = [
            'agency_name',
            'agency_label',
            'default_tone',
            'support_email',
            'provisioning_notes',
        ];

        $current = $this->getAgencyPreferences();
        $merged  = $current;

        foreach ($preferences as $key => $value) {
            if (! is_string($key) || ! in_array($key, $allowedKeys, true)) {
                $result['omitted']['preferences'][] = [
                    'key'    => (string) $key,
                    'reason' => 'key_not_allowed',
                ];
                continue;
            }

            if (array_key_exists($key, $current) && $current[$key] !== $value) {
                $result['omitted']['preferences'][] = [
                    'key'    => $key,
                    'reason' => 'local_preference_exists',
                ];
                continue;
            }

            $merged[$key] = is_scalar($value) ? $value : wp_json_encode($value);
            $result['applied']['preferences'][] = $key;
        }

        Settings::setConfig(self::AGENCY_PREFERENCES_KEY, (string) wp_json_encode($merged));
    }

    /**
     * @return string[]
     */
    private function getProvisionedPlaybookSlugs(): array
    {
        $raw = Settings::getConfig(self::PROVISIONED_PLAYBOOKS_KEY, '[]');
        $decoded = json_decode((string) $raw, true);

        return is_array($decoded) ? array_values(array_filter($decoded, 'is_string')) : [];
    }

    private function markPlaybookProvisioned(string $slug): void
    {
        $slugs = $this->getProvisionedPlaybookSlugs();
        if (! in_array($slug, $slugs, true)) {
            $slugs[] = $slug;
            Settings::setConfig(self::PROVISIONED_PLAYBOOKS_KEY, (string) wp_json_encode(array_values($slugs)));
        }
    }

    /**
     * @return array<string, mixed>
     */
    private function getAgencyPreferences(): array
    {
        $raw = Settings::getConfig(self::AGENCY_PREFERENCES_KEY, '{}');
        $decoded = json_decode((string) $raw, true);

        return is_array($decoded) ? $decoded : [];
    }

    /**
     * @param array<string, mixed> $result
     */
    private function logProvisioning(array $result): void
    {
        $appliedCount = count($result['applied']['playbooks'] ?? [])
            + count($result['applied']['skills'] ?? [])
            + count($result['applied']['preferences'] ?? []);
        $updatedCount = count($result['updated']['playbooks'] ?? [])
            + count($result['updated']['skills'] ?? []);
        $omittedCount = count($result['omitted']['playbooks'] ?? [])
            + count($result['omitted']['skills'] ?? [])
            + count($result['omitted']['preferences'] ?? []);

        if ($appliedCount + $updatedCount + $omittedCount === 0) {
            return;
        }

        $pairing = new PairingRegistrar();
        $label   = $pairing->getMasterLabel();
        $nodeId  = $pairing->getNodeId();
        $source  = $label !== '' ? 'hub:' . $label : 'hub:' . $nodeId;

        $logger = new ActionLogger();
        $logger->log(
            'hub_apply_agency_profile',
            sprintf(
                'Agency profile provisioning: %d applied, %d updated, %d omitted.',
                $appliedCount,
                $updatedCount,
                $omittedCount
            ),
            'success',
            $source,
            [
                'applied' => $result['applied'],
                'updated' => $result['updated'],
                'omitted' => $result['omitted'],
            ]
        );
    }

    private function sanitizeSlug(string $slug): string
    {
        if (function_exists('sanitize_title')) {
            $sanitized = sanitize_title($slug);
            if (is_string($sanitized) && $sanitized !== '') {
                return $sanitized;
            }
        }

        $slug = mb_strtolower($slug);
        $slug = preg_replace('/[^a-z0-9]+/', '-', $slug) ?? '';

        return trim($slug, '-');
    }
}
