<?php

namespace App\Services\HubSpot;

use App\Models\Committee;
use App\Models\Member;
use App\Models\WorkGroup;
use App\Models\User;
use Illuminate\Support\Facades\Log;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;

/**
 * HubSpot Sync Orchestrator
 *
 * Orchestrates the full synchronization process between the database
 * committees/workgroups and HubSpot contact lists.
 */
class HubSpotSyncOrchestrator
{
    /**
     * @var HubSpotApiClient
     */
    protected $apiClient;

    /**
     * @var HubSpotListService
     */
    protected $listService;

    /**
     * @var HubSpotContactService
     */
    protected $contactService;

    /**
     * @var HubSpotSyncMetrics
     */
    protected $metrics;

    /**
     * Create a new HubSpotSyncOrchestrator instance.
     *
     * @param HubSpotApiClient $apiClient
     */
    public function __construct(HubSpotApiClient $apiClient)
    {
        $this->apiClient = $apiClient;
        $this->listService = new HubSpotListService($apiClient);
        $this->contactService = new HubSpotContactService($apiClient);
        $this->metrics = new HubSpotSyncMetrics();
    }

    /**
     * Run the full synchronization process.
     *
     * @param bool $dryRun
     * @return HubSpotSyncMetrics
     */
    /**
     * Run the full synchronization process, with optional filtering by type.
     *
     * @param bool $dryRun
     * @param string|null $type 'committee', 'workgroup', 'member-regular', 'member-associate', or null for all
     * @return HubSpotSyncMetrics
     */
    public function syncAll(bool $dryRun = false, ?string $type = null): HubSpotSyncMetrics
    {
        $this->apiClient->setDryRun($dryRun);
        $this->metrics = new HubSpotSyncMetrics();
        $this->metrics->setDryRun($dryRun);
        $this->metrics->start();

        Log::info('Starting HubSpot sync' . ($dryRun ? ' (DRY-RUN)' : ''));

        try {
            // Initialize API client
            $this->apiClient->initialize();


            // Get data based on type
            $committees = [];
            $workGroups = [];
            $memberRegularUsers = [];
            $memberAssociateUsers = [];

            if ($type === 'committee') {
                $committees = $this->getCommitteesWithUsers();
            } elseif ($type === 'workgroup') {
                $workGroups = $this->getWorkGroupsWithUsers();
            } elseif ($type === 'member-regular') {
                $memberRegularUsers = $this->getMemberUsersByType('regular');
            } elseif ($type === 'member-associate') {
                $memberAssociateUsers = $this->getMemberUsersByType('associate');
            } else {
                // Sync all types
                $committees = $this->getCommitteesWithUsers();
                $workGroups = $this->getWorkGroupsWithUsers();
                $memberRegularUsers = $this->getMemberUsersByType('regular');
                $memberAssociateUsers = $this->getMemberUsersByType('associate');
            }

            $this->metrics->setTotalCommittees(count($committees));
            $this->metrics->setTotalWorkGroups(count($workGroups));

            // Count unique active users (from all lists)
            $allUserIds = [];
            foreach ($committees as $item) {
                foreach ($item['usersTo'] as $user) {
                    $allUserIds[$user['id']] = true;
                }
                foreach ($item['usersCc'] as $user) {
                    $allUserIds[$user['id']] = true;
                }
            }
            foreach ($workGroups as $item) {
                foreach ($item['usersTo'] as $user) {
                    $allUserIds[$user['id']] = true;
                }
                foreach ($item['usersCc'] as $user) {
                    $allUserIds[$user['id']] = true;
                }
            }
            // Add member users to the count
            foreach ($memberRegularUsers as $user) {
                $allUserIds[$user['id']] = true;
            }
            foreach ($memberAssociateUsers as $user) {
                $allUserIds[$user['id']] = true;
            }
            $this->metrics->setTotalUsers(count($allUserIds));

            // Build expected lists map: title => ['title' => ..., 'type' => ..., 'emails' => [...]]
            $expectedLists = $this->buildExpectedListsMap($committees, $workGroups, $memberRegularUsers, $memberAssociateUsers);

            // Determine which HubSpot lists to fetch based on sync type
            $listFilterType = $this->getListFilterType($type);
            $hubspotLists = $this->listService->getServerSyncListsMap($listFilterType);

            // Sync lists: create new, delete obsolete
            $listsCreated = $this->syncLists($expectedLists, $hubspotLists);

            // Refresh HubSpot lists map after creating new ones
            if (!$dryRun) {
                // If we created new lists, wait a moment for HubSpot to index them
                if ($listsCreated > 0) {
                    Log::info("Waiting 3 seconds for HubSpot to index newly created lists...");
                    sleep(3);
                }
                $hubspotLists = $this->listService->getServerSyncListsMap($listFilterType);
            } else {
                // In dry-run, simulate new lists
                foreach ($expectedLists as $title => $data) {
                    if (!isset($hubspotLists[$title])) {
                        $isMemberList = in_array($data['type'], ['member-regular', 'member-associate']);
                        $hubspotLists[$title] = [
                            'listId' => 'dry-run-' . md5($title),
                            'name' => $this->apiClient->buildListName($title, $isMemberList),
                            'title' => $title,
                            'size' => 0,
                            'dryRun' => true,
                            'listType' => $isMemberList ? 'member' : 'server',
                        ];
                    }
                }
            }

            // Sync contacts in each list
            $this->syncContacts($expectedLists, $hubspotLists);

        } catch (\Exception $e) {
            $this->metrics->recordError($e->getMessage(), 'syncAll');
            Log::error('HubSpot sync failed: ' . $e->getMessage());
        }

        $this->metrics->end();

        Log::info('HubSpot sync completed' . ($dryRun ? ' (DRY-RUN)' : ''));

        return $this->metrics;
    }

    /**
     * Get all committees with their active users, separated by TO and CC type.
     *
     * @return array Each item contains 'usersTo' and 'usersCc' arrays
     */
    protected function getCommitteesWithUsers(): array
    {
        $language = $this->apiClient->getTitleLanguage();
        $originalLocale = LaravelLocalization::getCurrentLocale();

        // Temporarily set locale to get English titles
        LaravelLocalization::setLocale($language);

        try {
            $committees = Committee::full()
                ->where('status', 'published')
                ->with(['users' => function ($query) {
                    $query->where('state', 'active');
                }, 'languages'])->get();

            $result = [];
            foreach ($committees as $committee) {
                $title = $committee->title;

                if (empty($title)) {
                    Log::warning("Committee ID {$committee->id} has no title in language '{$language}'");
                    continue;
                }

                $usersTo = [];
                $usersCc = [];

                foreach ($committee->users as $user) {
                    if (!empty($user->email)) {
                        $userData = [
                            'id' => $user->id,
                            'email' => strtolower(trim($user->email)),
                            'firstName' => $user->first_name,
                            'lastName' => $user->last_name,
                        ];

                        // Get type from pivot, default to 'to' if empty or null
                        $pivotType = $user->pivot->type ?? 'to';
                        $pivotType = empty($pivotType) ? 'to' : strtolower($pivotType);

                        if ($pivotType === 'cc') {
                            $usersCc[] = $userData;
                        } else {
                            $usersTo[] = $userData;
                        }
                    }
                }

                $result[] = [
                    'id' => $committee->id,
                    'title' => $title,
                    'type' => 'committee',
                    'usersTo' => $usersTo,
                    'usersCc' => $usersCc,
                ];
            }

            return $result;
        } finally {
            // Restore original locale
            LaravelLocalization::setLocale($originalLocale);
        }
    }

    /**
     * Get all workgroups with their active users, separated by TO and CC type.
     *
     * @return array Each item contains 'usersTo' and 'usersCc' arrays
     */
    protected function getWorkGroupsWithUsers(): array
    {
        $language = $this->apiClient->getTitleLanguage();
        $originalLocale = LaravelLocalization::getCurrentLocale();

        // Temporarily set locale to get English titles
        LaravelLocalization::setLocale($language);

        try {
            $workGroups = WorkGroup::full()
                ->where('status', 'published')
                ->with(['users' => function ($query) {
                    $query->where('state', 'active');
                }, 'languages'])->get();

            $result = [];
            foreach ($workGroups as $workGroup) {
                $title = $workGroup->title;

                if (empty($title)) {
                    Log::warning("WorkGroup ID {$workGroup->id} has no title in language '{$language}'");
                    continue;
                }

                $usersTo = [];
                $usersCc = [];

                foreach ($workGroup->users as $user) {
                    if (!empty($user->email)) {
                        $userData = [
                            'id' => $user->id,
                            'email' => strtolower(trim($user->email)),
                            'firstName' => $user->first_name,
                            'lastName' => $user->last_name,
                        ];

                        // Get type from pivot, default to 'to' if empty or null
                        $pivotType = $user->pivot->type ?? 'to';
                        $pivotType = empty($pivotType) ? 'to' : strtolower($pivotType);

                        if ($pivotType === 'cc') {
                            $usersCc[] = $userData;
                        } else {
                            $usersTo[] = $userData;
                        }
                    }
                }

                $result[] = [
                    'id' => $workGroup->id,
                    'title' => $title,
                    'type' => 'workgroup',
                    'usersTo' => $usersTo,
                    'usersCc' => $usersCc,
                ];
            }

            return $result;
        } finally {
            // Restore original locale
            LaravelLocalization::setLocale($originalLocale);
        }
    }

    /**
     * Get users from members with a specific member_type.
     *
     * @param string $memberType 'regular' or 'associate'
     * @return array Array of users ['id' => ..., 'email' => ..., 'name' => ...]
     */
    protected function getMemberUsersByType(string $memberType): array
    {
        $users = [];

        // Get all members with the specified type that have users
        $members = Member::where('member_type', $memberType)
            ->with(['users' => function ($query) {
                $query->where('state', 'active')
                      ->whereNotNull('email')
                      ->where('email', '!=', '');
            }])
            ->get();

        foreach ($members as $member) {
            foreach ($member->users as $user) {
                // Use user id as key to avoid duplicates
                $users[$user->id] = [
                    'id' => $user->id,
                    'email' => strtolower(trim($user->email)),
                    'name' => trim($user->name . ' ' . $user->surname),
                    'memberId' => $member->id,
                    'memberName' => $member->title ?? $member->name ?? 'Member #' . $member->id,
                ];
            }
        }

        Log::info("Found " . count($users) . " active users from {$memberType} members");

        return array_values($users);
    }

    /**
     * Get the list filter type for HubSpot lists based on sync type.
     *
     * @param string|null $type The sync type
     * @return string|null 'server', 'member', or null for all
     */
    protected function getListFilterType(?string $type): ?string
    {
        if ($type === 'committee' || $type === 'workgroup') {
            return 'server';
        }

        if ($type === 'member-regular' || $type === 'member-associate') {
            return 'member';
        }

        // For 'all' or null, return null to get all managed lists
        return null;
    }

    /**
     * Build expected lists map from committees, workgroups, and member users.
     * Creates two lists per committee/workgroup: one for TO recipients and one for CC recipients.
     * Creates two member lists: one for regular members and one for associate members.
     *
     * @param array $committees
     * @param array $workGroups
     * @param array $memberRegularUsers
     * @param array $memberAssociateUsers
     * @return array ['title' => ['title' => ..., 'type' => ..., 'emails' => [...]]]
     */
    protected function buildExpectedListsMap(array $committees, array $workGroups, array $memberRegularUsers = [], array $memberAssociateUsers = []): array
    {
        $expectedLists = [];

        // Add committees (both TO and CC lists)
        foreach ($committees as $committee) {
            $title = $committee['title'];

            // Create TO list
            $listTitleTo = "Committee: {$title} [TO]";
            $emailsTo = array_map(function ($user) {
                return $user['email'];
            }, $committee['usersTo']);

            $expectedLists[$listTitleTo] = [
                'title' => $listTitleTo,
                'originalTitle' => $title,
                'type' => 'committee',
                'recipientType' => 'to',
                'modelId' => $committee['id'],
                'emails' => array_unique($emailsTo),
            ];

            // Create CC list
            $listTitleCc = "Committee: {$title} [CC]";
            $emailsCc = array_map(function ($user) {
                return $user['email'];
            }, $committee['usersCc']);

            $expectedLists[$listTitleCc] = [
                'title' => $listTitleCc,
                'originalTitle' => $title,
                'type' => 'committee',
                'recipientType' => 'cc',
                'modelId' => $committee['id'],
                'emails' => array_unique($emailsCc),
            ];
        }

        // Add workgroups (both TO and CC lists)
        foreach ($workGroups as $workGroup) {
            $title = $workGroup['title'];

            // Create TO list
            $listTitleTo = "WorkGroup: {$title} [TO]";
            $emailsTo = array_map(function ($user) {
                return $user['email'];
            }, $workGroup['usersTo']);

            $expectedLists[$listTitleTo] = [
                'title' => $listTitleTo,
                'originalTitle' => $title,
                'type' => 'workgroup',
                'recipientType' => 'to',
                'modelId' => $workGroup['id'],
                'emails' => array_unique($emailsTo),
            ];

            // Create CC list
            $listTitleCc = "WorkGroup: {$title} [CC]";
            $emailsCc = array_map(function ($user) {
                return $user['email'];
            }, $workGroup['usersCc']);

            $expectedLists[$listTitleCc] = [
                'title' => $listTitleCc,
                'originalTitle' => $title,
                'type' => 'workgroup',
                'recipientType' => 'cc',
                'modelId' => $workGroup['id'],
                'emails' => array_unique($emailsCc),
            ];
        }

        // Add Members Regular list
        if (!empty($memberRegularUsers)) {
            $listTitle = "Users Members Regular";
            $emails = array_map(function ($user) {
                return $user['email'];
            }, $memberRegularUsers);

            $expectedLists[$listTitle] = [
                'title' => $listTitle,
                'originalTitle' => 'Members Regular',
                'type' => 'member-regular',
                'recipientType' => null,
                'modelId' => null,
                'emails' => array_unique($emails),
            ];
        }

        // Add Members Associate list
        if (!empty($memberAssociateUsers)) {
            $listTitle = "Users Members Associate";
            $emails = array_map(function ($user) {
                return $user['email'];
            }, $memberAssociateUsers);

            $expectedLists[$listTitle] = [
                'title' => $listTitle,
                'originalTitle' => 'Members Associate',
                'type' => 'member-associate',
                'recipientType' => null,
                'modelId' => null,
                'emails' => array_unique($emails),
            ];
        }

        return $expectedLists;
    }

    /**
     * Sync lists: create missing, delete obsolete.
     *
     * @param array $expectedLists
     * @param array $hubspotLists
     * @return int Number of lists created
     */
    protected function syncLists(array $expectedLists, array $hubspotLists): int
    {
        // Find lists to create (in expected but not in HubSpot)
        $toCreate = array_diff_key($expectedLists, $hubspotLists);

        // Find lists to delete (in HubSpot but not in expected)
        $toDelete = array_diff_key($hubspotLists, $expectedLists);

        // Create missing lists
        $createdCount = 0;
        foreach ($toCreate as $title => $data) {
            try {
                // Determine if this is a member list
                $isMemberList = in_array($data['type'], ['member-regular', 'member-associate']);
                Log::info("Creating HubSpot list: {$title}" . ($isMemberList ? ' (member list)' : ''));
                $this->listService->createList($title, $isMemberList);
                $this->metrics->recordListCreated($title);
                $createdCount++;
            } catch (\Exception $e) {
                $this->metrics->recordError("Failed to create list: {$title} - " . $e->getMessage(), 'createList');
                Log::error("Failed to create HubSpot list '{$title}': " . $e->getMessage());
            }
        }

        // Delete obsolete lists
        foreach ($toDelete as $title => $listData) {
            try {
                Log::info("Deleting HubSpot list: {$title}");
                $this->listService->deleteList($listData['listId']);
                $this->metrics->recordListDeleted($title);
            } catch (\Exception $e) {
                $this->metrics->recordError("Failed to delete list: {$title} - " . $e->getMessage(), 'deleteList');
                Log::error("Failed to delete HubSpot list '{$title}': " . $e->getMessage());
            }
        }

        return $createdCount;
    }

    /**
     * Sync contacts in each list.
     *
     * @param array $expectedLists
     * @param array $hubspotLists
     * @return void
     */
    protected function syncContacts(array $expectedLists, array $hubspotLists): void
    {
        $isDryRun = $this->apiClient->isDryRun();

        foreach ($expectedLists as $title => $data) {
            if (!isset($hubspotLists[$title])) {
                Log::warning("List not found in HubSpot for contact sync: {$title}");
                continue;
            }

            $listData = $hubspotLists[$title];
            $listId = $listData['listId'];
            $expectedEmails = $data['emails'];

            // In dry-run mode with simulated lists, skip API calls for contacts
            if ($isDryRun && isset($listData['dryRun']) && $listData['dryRun']) {
                // Simulate: all expected emails would be added (new list)
                Log::info("[DRY-RUN] Would sync contacts for new list: {$title}");
                $this->metrics->recordListProcessed($title);
                if (!empty($expectedEmails)) {
                    $this->metrics->recordContactsAdded($title, $expectedEmails);
                }
                continue;
            }

            try {
                Log::info("Syncing contacts for list: {$title} ({$listId})");

                $result = $this->contactService->syncContactsInList($listId, $expectedEmails);

                $this->metrics->recordListProcessed($title);

                if (!empty($result['added'])) {
                    $this->metrics->recordContactsAdded($title, $result['added']);
                }

                if (!empty($result['removed'])) {
                    $this->metrics->recordContactsRemoved($title, $result['removed']);
                }

                if (!empty($result['kept'])) {
                    $this->metrics->recordContactsKept($title, count($result['kept']));
                }

                if (!empty($result['skipped'])) {
                    $this->metrics->recordContactsSkipped($title, $result['skipped']);
                }

                if (!empty($result['errors'])) {
                    foreach ($result['errors'] as $error) {
                        $this->metrics->recordError($error, "syncContacts: {$title}");
                    }
                }

            } catch (\Exception $e) {
                $this->metrics->recordError("Failed to sync contacts for list: {$title} - " . $e->getMessage(), 'syncContacts');
                Log::error("Failed to sync contacts for HubSpot list '{$title}': " . $e->getMessage());
            }
        }
    }

    /**
     * Get the metrics instance.
     *
     * @return HubSpotSyncMetrics
     */
    public function getMetrics(): HubSpotSyncMetrics
    {
        return $this->metrics;
    }

    /**
     * Get the list service instance.
     *
     * @return HubSpotListService
     */
    public function getListService(): HubSpotListService
    {
        return $this->listService;
    }

    /**
     * Get the contact service instance.
     *
     * @return HubSpotContactService
     */
    public function getContactService(): HubSpotContactService
    {
        return $this->contactService;
    }
}
