<?php

namespace App\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

/**
 * Thin client over the Meta WhatsApp Cloud API used to manage message templates
 * for the single, shared WhatsApp Business Account (WABA).
 *
 * Credentials live in config/services.php => 'whatsapp' (.env).
 */
class WhatsappCloudService
{
    /**
     * Predefined template variables franchisees/admins can use, with the sample
     * value Meta requires for each. Keys are the {{token}} names.
     */
    public const VARIABLE_EXAMPLES = [
        'name' => 'John',
        'parent_name' => 'John',
        'parent_first_name' => 'John',
        'parent_last_name' => 'Doe',
        'parent_email' => 'john@example.com',
        'child_name' => 'Alex',
        'age' => '7',
        'location' => 'Downtown',
        'program' => 'Robotics',
    ];

    private Client $http;

    public function __construct()
    {
        $this->http = new Client([
            'timeout' => 20,
            'verify' => false,
        ]);
    }

    private function config(string $key): ?string
    {
        $value = config("services.whatsapp.$key");
        return $value !== null ? (string) $value : null;
    }

    private function accessToken(): string
    {
        $token = $this->config('access_token');
        if (!$token) {
            throw new \RuntimeException('WhatsApp access token missing in config/services.php (.env).');
        }
        return $token;
    }

    private function wabaId(): string
    {
        $waba = $this->config('waba_id');
        if (!$waba) {
            throw new \RuntimeException('WhatsApp Business Account id (WHATSAPP_WABA_ID) is not configured.');
        }
        return $waba;
    }

    private function baseUrl(): string
    {
        $base = rtrim($this->config('graph_base_url') ?: 'https://graph.facebook.com', '/');
        $version = $this->config('graph_version') ?: 'v21.0';
        return "{$base}/{$version}";
    }

    /**
     * Build Meta "components" array from the individual template parts.
     *
     * @param array{header_text?:?string, body_text:string, footer_text?:?string, buttons?:array, body_examples?:array} $parts
     */
    public function buildComponents(array $parts): array
    {
        $components = [];

        if (!empty($parts['header_text'])) {
            $components[] = [
                'type' => 'HEADER',
                'format' => 'TEXT',
                'text' => $parts['header_text'],
            ];
        }

        $body = [
            'type' => 'BODY',
            'text' => $parts['body_text'],
        ];

        // Meta requires a sample value for every variable in the body. Named
        // variables ({{parent_name}}) use body_text_named_params; positional
        // variables ({{1}}) use body_text.
        preg_match_all('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', (string) ($parts['body_text'] ?? ''), $m);
        $tokens = array_values(array_unique($m[1] ?? []));

        if (!empty($tokens)) {
            $provided = (array) ($parts['body_examples'] ?? []);
            $named = false;
            foreach ($tokens as $t) {
                if (!ctype_digit((string) $t)) {
                    $named = true;
                    break;
                }
            }

            if ($named) {
                $body['example'] = [
                    'body_text_named_params' => array_map(function ($token) use ($provided) {
                        $example = $provided[$token] ?? self::VARIABLE_EXAMPLES[$token] ?? 'Sample';
                        return ['param_name' => $token, 'example' => (string) $example];
                    }, $tokens),
                ];
            } else {
                $values = array_map(
                    fn($token) => (string) ($provided[$token - 1] ?? $provided[$token] ?? 'Sample'),
                    $tokens
                );
                $body['example'] = ['body_text' => [$values]];
            }
        }

        $components[] = $body;

        if (!empty($parts['footer_text'])) {
            $components[] = [
                'type' => 'FOOTER',
                'text' => $parts['footer_text'],
            ];
        }

        if (!empty($parts['buttons']) && is_array($parts['buttons'])) {
            $components[] = [
                'type' => 'BUTTONS',
                'buttons' => array_values($parts['buttons']),
            ];
        }

        return $components;
    }

    /**
     * Create (submit) a message template. Meta returns the new template id and its
     * initial review status (usually PENDING).
     *
     * @return array{id:?string, status:?string, category:?string}
     */
    public function createMessageTemplate(string $name, string $language, string $category, array $components): array
    {
        $url = $this->baseUrl() . '/' . rawurlencode($this->wabaId()) . '/message_templates';

        $data = $this->request('POST', $url, [
            'json' => [
                'name' => $name,
                'language' => $language,
                'category' => $category,
                'parameter_format' => $this->parameterFormat($components),
                'components' => $components,
            ],
        ]);

        return [
            'id' => $data['id'] ?? null,
            'status' => $data['status'] ?? null,
            'category' => $data['category'] ?? null,
        ];
    }

    /**
     * Edit an existing template in place (Meta's POST /{template_id}). Used to resubmit a
     * rejected/approved template without deleting it — avoids the "content is being deleted"
     * race that delete-then-recreate hits. Editing resets the template to PENDING review.
     * Name and language can't change here (immutable on Meta) — use create for a rename.
     */
    public function editMessageTemplate(string $templateId, string $category, array $components): array
    {
        $url = $this->baseUrl() . '/' . rawurlencode($templateId);

        $this->request('POST', $url, [
            'json' => [
                'category' => $category,
                'parameter_format' => $this->parameterFormat($components),
                'components' => $components,
            ],
        ]);

        return [
            'id' => $templateId,
            'status' => 'PENDING', // editing sends it back for review
            'category' => $category,
        ];
    }

    /**
     * NAMED ({{name}}) vs POSITIONAL ({{1}}) — Meta needs this explicitly, else it defaults
     * to POSITIONAL and rejects named placeholders.
     */
    private function parameterFormat(array $components): string
    {
        foreach ($components as $component) {
            if (($component['type'] ?? '') === 'BODY' && isset($component['example']['body_text_named_params'])) {
                return 'NAMED';
            }
        }
        return 'POSITIONAL';
    }

    /**
     * Send an approved template message to a customer.
     *
     * @param string $to         Recipient phone in international format (digits, country code, no '+').
     * @param string $templateName  The Meta template name (whatsapp_templates.meta_name).
     * @param string $language   Template language code, e.g. "en_US".
     * @param array  $bodyParams Ordered values for the body {{1}}, {{2}}... variables.
     * @return array Meta response (includes the sent message id under messages[0].id).
     */
    public function sendTemplateMessage(string $to, string $templateName, string $language, array $bodyParams = []): array
    {
        $phoneNumberId = $this->config('phone_number_id');
        if (!$phoneNumberId) {
            throw new \RuntimeException('WhatsApp phone number id (WHATSAPP_PHONE_NUMBER_ID) is not configured.');
        }

        $template = [
            'name' => $templateName,
            'language' => ['code' => $language],
        ];

        if (!empty($bodyParams)) {
            // Positional params arrive as a 0-indexed list ({{1}}); named params
            // arrive as an associative map keyed by variable name ({{name}}).
            $isNamed = array_keys($bodyParams) !== range(0, count($bodyParams) - 1);

            $parameters = [];
            foreach ($bodyParams as $key => $value) {
                $param = ['type' => 'text', 'text' => (string) $value];
                if ($isNamed) {
                    $param['parameter_name'] = (string) $key;
                }
                $parameters[] = $param;
            }

            $template['components'] = [[
                'type' => 'body',
                'parameters' => $parameters,
            ]];
        }

        $url = $this->baseUrl() . '/' . rawurlencode($phoneNumberId) . '/messages';

        return $this->request('POST', $url, [
            'json' => [
                'messaging_product' => 'whatsapp',
                'to' => $to,
                'type' => 'template',
                'template' => $template,
            ],
        ]);
    }

    /**
     * Fetch a template's current state from Meta by name (used for manual sync).
     */
    public function getMessageTemplate(string $name): ?array
    {
        $url = $this->baseUrl() . '/' . rawurlencode($this->wabaId()) . '/message_templates';

        $data = $this->request('GET', $url, [
            'query' => ['name' => $name],
        ]);

        return $data['data'][0] ?? null;
    }

    /**
     * Fetch ALL templates on the WABA from Meta (follows cursor paging), so callers can
     * sync live statuses (PENDING / APPROVED / REJECTED) in a single sweep instead of
     * one request per template. Returns a flat list of Meta template objects.
     */
    public function listMessageTemplates(int $perPage = 200, int $maxPages = 15): array
    {
        $url = $this->baseUrl() . '/' . rawurlencode($this->wabaId()) . '/message_templates';
        $options = ['query' => ['limit' => $perPage, 'fields' => 'name,status,category,language,id']];

        $all = [];
        for ($page = 0; $page < $maxPages; $page++) {
            $data = $this->request('GET', $url, $options);
            foreach (($data['data'] ?? []) as $row) {
                $all[] = $row;
            }

            $next = $data['paging']['next'] ?? null;
            if (!$next) {
                break;
            }
            // Meta returns a full URL (with cursor) for the next page.
            $url = $next;
            $options = [];
        }

        return $all;
    }

    /**
     * Whether a template with this exact name already exists on the WABA (Meta side),
     * regardless of what our local database holds.
     */
    public function templateExistsOnMeta(string $name): bool
    {
        return $this->getMessageTemplate($name) !== null;
    }

    /**
     * Delete a template from the WABA by name.
     */
    public function deleteMessageTemplate(string $name): void
    {
        $url = $this->baseUrl() . '/' . rawurlencode($this->wabaId()) . '/message_templates';

        $this->request('DELETE', $url, [
            'query' => ['name' => $name],
        ]);
    }

    /**
     * Perform a Graph API request with the bearer token and return the decoded body.
     */
    private function request(string $method, string $url, array $options): array
    {
        $options['headers'] = array_merge($options['headers'] ?? [], [
            'Authorization' => 'Bearer ' . $this->accessToken(),
            'Accept' => 'application/json',
        ]);

        try {
            $res = $this->http->request($method, $url, $options);
            $body = (string) $res->getBody();
            return $body ? (json_decode($body, true) ?: []) : [];
        } catch (RequestException $e) {
            throw $this->toCleanException($e);
        }
    }

    private function toCleanException(RequestException $e): \RuntimeException
    {
        $status = $e->getResponse()?->getStatusCode();
        $body = (string) ($e->getResponse()?->getBody() ?? '');

        $message = 'WhatsApp Cloud API request failed';
        if ($body) {
            $decoded = json_decode($body, true);
            $metaMessage = $decoded['error']['error_user_msg']
                ?? $decoded['error']['message']
                ?? null;
            if ($metaMessage) {
                $message = $metaMessage;
            }
        }
        if ($status) {
            $message .= " (HTTP {$status})";
        }

        return new \RuntimeException($message, previous: $e);
    }
}
