<?php

namespace App\Services\HubSpot;

use HubSpot\Factory;
use HubSpot\Client\Crm\Lists\ApiException as ListsApiException;
use HubSpot\Client\Crm\Contacts\ApiException as ContactsApiException;
use Illuminate\Support\Facades\Log;

/**
 * HubSpot API Client
 *
 * Base client for HubSpot API interactions with connection management,
 * rate limiting, and retry logic.
 */
class HubSpotApiClient
{
    /**
     * @var \HubSpot\Discovery\Discovery|null
     */
    protected $client = null;

    /**
     * @var bool
     */
    protected $dryRun = false;

    /**
     * @var array
     */
    protected $config;

    /**
     * Create a new HubSpotApiClient instance.
     */
    public function __construct()
    {
        $this->config = config('hubspot');
    }

    /**
     * Check if HubSpot sync is available and properly configured.
     *
     * @return bool
     */
    public static function isAvailable(): bool
    {
        $accessToken = config('hubspot.access_token');
        $syncEnabled = config('hubspot.sync_enabled', false);

        return $syncEnabled && !empty($accessToken);
    }

    /**
     * Initialize the HubSpot client.
     *
     * @return self
     * @throws \Exception
     */
    public function initialize(): self
    {
        if (!self::isAvailable()) {
            throw new \Exception('HubSpot sync is not available. Check HUBSPOT_ACCESS_TOKEN and HUBSPOT_SYNC_ENABLED configuration.');
        }

        $this->client = Factory::createWithAccessToken($this->config['access_token']);

        return $this;
    }

    /**
     * Get the HubSpot client instance.
     *
     * @return \HubSpot\Discovery\Discovery
     * @throws \Exception
     */
    public function getClient(): \HubSpot\Discovery\Discovery
    {
        if ($this->client === null) {
            $this->initialize();
        }

        return $this->client;
    }

    /**
     * Set dry-run mode.
     *
     * @param bool $dryRun
     * @return self
     */
    public function setDryRun(bool $dryRun): self
    {
        $this->dryRun = $dryRun;
        return $this;
    }

    /**
     * Check if running in dry-run mode.
     *
     * @return bool
     */
    public function isDryRun(): bool
    {
        return $this->dryRun;
    }

    /**
     * Test the HubSpot API connection.
     *
     * @return array
     */
    public function testConnection(): array
    {
        try {
            $client = $this->getClient();

            // Try to fetch account info by getting contacts (limited to 1)
            $response = $client->crm()->contacts()->basicApi()->getPage(1);

            return [
                'success' => true,
                'message' => 'Successfully connected to HubSpot API.',
            ];
        } catch (\Exception $e) {
            return [
                'success' => false,
                'message' => 'Failed to connect to HubSpot API: ' . $e->getMessage(),
                'error' => $e,
            ];
        }
    }

    /**
     * Apply rate limiting delay between API calls.
     *
     * @param int|null $delayMs Custom delay in milliseconds (uses config if null)
     * @return void
     */
    public function applyRateLimit(?int $delayMs = null): void
    {
        $delay = $delayMs ?? $this->config['rate_limit']['delay_ms'] ?? 200;
        usleep($delay * 1000); // Convert ms to microseconds
    }

    /**
     * Execute an API call with retry logic and exponential backoff.
     *
     * @param callable $callback
     * @param string $operation Description of the operation for logging
     * @return mixed
     * @throws \Exception
     */
    public function executeWithRetry(callable $callback, string $operation = 'API call')
    {
        $maxRetries = $this->config['rate_limit']['max_retries'] ?? 3;
        $baseDelay = $this->config['rate_limit']['retry_delay_ms'] ?? 1000;
        $attempt = 0;
        $lastException = null;

        while ($attempt < $maxRetries) {
            try {
                $result = $callback();
                return $result;
            } catch (ListsApiException | ContactsApiException $e) {
                $lastException = $e;
                $attempt++;

                // Check if it's a rate limit error (429)
                if ($e->getCode() === 429 && $attempt < $maxRetries) {
                    $delay = $baseDelay * pow(2, $attempt - 1); // Exponential backoff
                    Log::warning("HubSpot rate limit hit for {$operation}. Retrying in {$delay}ms (attempt {$attempt}/{$maxRetries})");
                    usleep($delay * 1000);
                    continue;
                }

                // For other errors, log and throw
                Log::error("HubSpot API error during {$operation}: " . $e->getMessage());
                throw $e;
            } catch (\Exception $e) {
                $lastException = $e;
                $attempt++;

                if ($attempt < $maxRetries) {
                    $delay = $baseDelay * pow(2, $attempt - 1);
                    Log::warning("HubSpot error for {$operation}. Retrying in {$delay}ms (attempt {$attempt}/{$maxRetries}): " . $e->getMessage());
                    usleep($delay * 1000);
                    continue;
                }

                throw $e;
            }
        }

        throw $lastException ?? new \Exception("Failed after {$maxRetries} attempts: {$operation}");
    }

    /**
     * Get the list suffix used to identify server-managed lists (committees/workgroups).
     *
     * @return string
     */
    public function getListSuffix(): string
    {
        return $this->config['list_suffix'] ?? '(Server Sync)';
    }

    /**
     * Get the list suffix used to identify server-managed member lists.
     *
     * @return string
     */
    public function getMemberListSuffix(): string
    {
        return $this->config['member_list_suffix'] ?? '(Member Sync)';
    }

    /**
     * Get the list prefix (if configured).
     *
     * @return string
     */
    public function getListPrefix(): string
    {
        return $this->config['list_prefix'] ?? '';
    }

    /**
     * Build the full list name for a committee or workgroup.
     *
     * @param string $title
     * @param bool $isMemberList Whether this is a member list (uses different suffix)
     * @return string
     */
    public function buildListName(string $title, bool $isMemberList = false): string
    {
        $prefix = $this->getListPrefix();
        $suffix = $isMemberList ? $this->getMemberListSuffix() : $this->getListSuffix();

        $name = $title;

        if (!empty($prefix)) {
            $name = $prefix . ' ' . $name;
        }

        if (!empty($suffix)) {
            $name = $name . ' ' . $suffix;
        }

        return trim($name);
    }

    /**
     * Extract the original title from a full list name.
     *
     * @param string $listName
     * @return string
     */
    public function extractTitleFromListName(string $listName): string
    {
        $prefix = $this->getListPrefix();
        $suffix = $this->getListSuffix();
        $memberSuffix = $this->getMemberListSuffix();

        $title = $listName;

        // Remove suffix (check member suffix first, then regular suffix)
        if (!empty($memberSuffix) && str_ends_with($title, ' ' . $memberSuffix)) {
            $title = substr($title, 0, -strlen(' ' . $memberSuffix));
        } elseif (!empty($suffix) && str_ends_with($title, ' ' . $suffix)) {
            $title = substr($title, 0, -strlen(' ' . $suffix));
        }

        // Remove prefix
        if (!empty($prefix) && str_starts_with($title, $prefix . ' ')) {
            $title = substr($title, strlen($prefix . ' '));
        }

        return trim($title);
    }

    /**
     * Check if a list name matches the server sync pattern (committees/workgroups).
     *
     * @param string $listName
     * @return bool
     */
    public function isServerSyncList(string $listName): bool
    {
        $suffix = $this->getListSuffix();
        return !empty($suffix) && str_ends_with($listName, ' ' . $suffix);
    }

    /**
     * Check if a list name matches the member sync pattern.
     *
     * @param string $listName
     * @return bool
     */
    public function isMemberSyncList(string $listName): bool
    {
        $suffix = $this->getMemberListSuffix();
        return !empty($suffix) && str_ends_with($listName, ' ' . $suffix);
    }

    /**
     * Check if a list name is any kind of server-managed list.
     *
     * @param string $listName
     * @return bool
     */
    public function isAnyServerManagedList(string $listName): bool
    {
        return $this->isServerSyncList($listName) || $this->isMemberSyncList($listName);
    }

    /**
     * Get the configured batch size.
     *
     * @return int
     */
    public function getBatchSize(): int
    {
        return $this->config['rate_limit']['batch_size'] ?? 100;
    }

    /**
     * Get the language code for titles.
     *
     * @return string
     */
    public function getTitleLanguage(): string
    {
        return $this->config['title_language'] ?? 'en';
    }
}
