<?php

namespace SoftminalB2B\Http\Controllers;

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

use SoftminalB2B\Messaging\Emails;
use SoftminalB2B\Messaging\Presenter;
use SoftminalB2B\Models\Conversation;
use SoftminalB2B\Models\Message;

/**
 * Buyer side of Communication & Negotiations ([[project-messaging-feature]]).
 *
 * The route group is `can:read` (any logged-in customer). A buyer reads and
 * continues their own threads — and, on Pro, threads shared with their company.
 * Every method re-checks access against the current user and treats another
 * buyer's thread as not-found (its existence is never leaked).
 */
class BuyerMessagesController extends Controller
{
    private const MAX_LIST    = 100;
    private const MAX_BODY    = 5000;
    private const MAX_SUBJECT = 191;

    /**
     * The buyer's conversations, newest activity first, each with an unread
     * flag for the buyer.
     */
    public function index(\WP_REST_Request $request, $params = [])
    {
        $userId = get_current_user_id();

        $conversations = iterator_to_array(
            Conversation::where('buyer_id', $userId)
                ->orderBy('last_message_at', 'DESC')
                ->limit(self::MAX_LIST)
                ->get()
        );

        $companyId = $this->companyId($userId);

        if ($companyId !== null) {
            $shared = iterator_to_array(
                Conversation::where('company_id', $companyId)
                    ->andWhere('buyer_id', '!=', $userId)
                    ->orderBy('last_message_at', 'DESC')
                    ->limit(self::MAX_LIST)
                    ->get()
            );

            $conversations = array_merge($conversations, $shared);

            usort($conversations, static function (Conversation $a, Conversation $b) {
                return strcmp((string) $b->last_message_at, (string) $a->last_message_at);
            });
        }

        // Prime the page's buyers in one query (custom-table rows don't prime the
        // user cache), so Presenter::summary()'s per-row get_userdata() is a hit.
        // Company-shared threads can each carry a different buyer.
        $buyerIds = array_values(array_unique(array_filter(
            array_map(static fn (Conversation $c) => (int) $c->buyer_id, $conversations)
        )));
        if (!empty($buyerIds)) {
            cache_users($buyerIds);
        }

        $rows = array_map(
            static fn (Conversation $c) => Presenter::summary($c, Message::AUTHOR_BUYER),
            $conversations
        );

        return $this->success(['conversations' => $rows]);
    }

    /**
     * One thread. Opening it advances the buyer's read cursor.
     */
    public function show(\WP_REST_Request $request, $params = [])
    {
        $conversation = $this->accessibleOrNull($request);

        if (!$conversation) {
            return $this->notFound();
        }

        $conversation->markRead(Message::AUTHOR_BUYER);

        return $this->success(['conversation' => Presenter::thread($conversation, Message::AUTHOR_BUYER)]);
    }

    /**
     * Start a new conversation with an opening message, then email the shop.
     * Anchors (order/product/quote) are accepted so Pro entry points can attach
     * context; the free centre sends a subject + body only.
     */
    public function store(\WP_REST_Request $request, $params = [])
    {
        $body = $this->cleanBody($request->get_param('body'));

        if ($body === '') {
            return $this->error(__('Write your message before sending.', 'softminal-b2b-wholesale-for-woocommerce'), 422);
        }

        $userId = get_current_user_id();

        if (Conversation::where('buyer_id', $userId)->count() >= self::MAX_LIST) {
            return $this->error(__('You have reached the maximum number of conversations.', 'softminal-b2b-wholesale-for-woocommerce'), 422);
        }

        [$anchorType, $anchorId] = $this->cleanAnchor($request);

        $conversation = Conversation::create([
            'subject'     => $this->cleanSubject($request->get_param('subject')),
            'buyer_id'    => $userId,
            'company_id'  => $this->companyId($userId),
            'anchor_type' => $anchorType,
            'anchor_id'   => $anchorId,
            'status'      => Conversation::STATUS_OPEN,
        ]);

        $message = $conversation->addMessage($userId, Message::AUTHOR_BUYER, $body);

        Emails::notifyShop($conversation, $message);

        do_action('smb2b_message_sent', $conversation, $message);

        return $this->success(
            ['conversation' => Presenter::thread($conversation, Message::AUTHOR_BUYER)],
            __('Message sent.', 'softminal-b2b-wholesale-for-woocommerce')
        );
    }

    /**
     * Reply to an existing thread as the buyer, then email the shop.
     */
    public function reply(\WP_REST_Request $request, $params = [])
    {
        $conversation = $this->accessibleOrNull($request);

        if (!$conversation) {
            return $this->notFound();
        }

        $body = $this->cleanBody($request->get_param('body'));

        if ($body === '') {
            return $this->error(__('Write a message before sending.', 'softminal-b2b-wholesale-for-woocommerce'), 422);
        }

        if ($conversation->status === Conversation::STATUS_RESOLVED) {
            $conversation->status = Conversation::STATUS_OPEN;
            $conversation->save();
        }

        $userId = get_current_user_id();

        // Quote-anchored threads are owned by the quote flow (Pro): route through
        // it so the buyer's reply reaches the negotiation (and its email/debounce)
        // exactly as a reply from the quote drawer would, with no divergence.
        if (apply_filters('smb2b_conversation_reply_handled', false, $conversation, $userId, Message::AUTHOR_BUYER, $body)) {
            $conversation = Conversation::find((int) $conversation->id);

            return $this->success(
                ['conversation' => Presenter::thread($conversation, Message::AUTHOR_BUYER)],
                __('Message sent.', 'softminal-b2b-wholesale-for-woocommerce')
            );
        }

        $message = $conversation->addMessage($userId, Message::AUTHOR_BUYER, $body);

        Emails::notifyShop($conversation, $message);

        do_action('smb2b_message_sent', $conversation, $message);

        return $this->success(
            ['conversation' => Presenter::thread($conversation, Message::AUTHOR_BUYER)],
            __('Message sent.', 'softminal-b2b-wholesale-for-woocommerce')
        );
    }

    /**
     * Unread-thread count for the storefront dot (My Account menu).
     */
    public function unreadCount(\WP_REST_Request $request, $params = [])
    {
        return $this->success(['count' => Conversation::unreadThreadCountForBuyer(get_current_user_id())]);
    }

    /**
     * A thread the current user may read: their own, or (Pro) one belonging to
     * their company.
     */
    private function accessibleOrNull(\WP_REST_Request $request): ?Conversation
    {
        $id           = absint($request->get_param('id'));
        $conversation = $id ? Conversation::find($id) : null;

        if (!$conversation) {
            return null;
        }

        $userId = get_current_user_id();

        if ((int) $conversation->buyer_id === $userId) {
            return $conversation;
        }

        $companyId = $this->companyId($userId);

        if ($companyId !== null && (int) $conversation->company_id === $companyId) {
            return $conversation;
        }

        return null;
    }

    /**
     * The user's company id via the same seam pricing uses (Pro answers it;
     * null on Free or for buyers outside a company).
     */
    private function companyId(int $userId): ?int
    {
        $membership = apply_filters('smb2b_pricing_context_company', null, $userId);

        return is_array($membership) && !empty($membership['company_id'])
            ? (int) $membership['company_id']
            : null;
    }

    /**
     * @return array{0:string, 1:int}
     */
    private function cleanAnchor(\WP_REST_Request $request): array
    {
        $type = sanitize_key((string) $request->get_param('anchor_type'));
        $id   = absint($request->get_param('anchor_id'));

        if (!in_array($type, Conversation::ANCHORS, true) || $id <= 0) {
            return ['', 0];
        }

        return [$type, $id];
    }

    private function cleanSubject($raw): string
    {
        $subject = trim(sanitize_text_field((string) $raw));

        return function_exists('mb_substr')
            ? mb_substr($subject, 0, self::MAX_SUBJECT)
            : substr($subject, 0, self::MAX_SUBJECT);
    }

    private function cleanBody($raw): string
    {
        $body = trim(sanitize_textarea_field((string) $raw));

        return function_exists('mb_substr') ? mb_substr($body, 0, self::MAX_BODY) : substr($body, 0, self::MAX_BODY);
    }
}
