<?php

namespace App\Services\MicrosoftGraph;

use App\Models\ContactFolder;
use App\Models\Contact;
use App\Models\User;
use App\Models\Committee;
use App\Models\MicrosoftUser;
use App\Models\WorkGroup;
use App\Services\MicrosoftGraph\MicrosoftGraphApiClient;
use App\Services\MicrosoftGraph\MicrosoftGraphUtilities;
use Illuminate\Support\Facades\Log;
use Exception;

/**
 * Service for managing Microsoft Graph contacts
 * Handles CRUD operations for individual contacts within folders
 */
class MicrosoftGraphContactService
{
    protected $apiClient;

    public function __construct(MicrosoftGraphApiClient $apiClient)
    {
        $this->apiClient = $apiClient;
    }

    /**
     * Sync all contacts across all contact folders
     */
    public function syncContacts(bool $dryRun = false, bool $onlyNewUsers = false): array
    {
        $this->apiClient->setDryRun($dryRun);

        MicrosoftGraphUtilities::logOperation('sync', 'Starting contacts synchronization', [
            'dry_run' => $dryRun
        ]);

        $stats = [
            'folders_processed' => 0,
            'contacts_created' => 0,
            'contacts_updated' => 0,
            'contacts_removed' => 0,
            'contacts_failed' => 0,
            'errors' => []
        ];

        try {
            // Get contact folders based on the onlyNewUsers parameter
            if ($onlyNewUsers) {
                // First, get Microsoft users with is_new = true
                $newUserIds = MicrosoftUser::whereNotNull('email')->where('is_new', true)->pluck('id');

                // Then get contact folders for those users
                $contactFolders = ContactFolder::whereNotNull('ms_folder_id')
                                    ->whereIn('microsoft_user_id', $newUserIds)
                                    ->get();

                MicrosoftGraphUtilities::logOperation('info', "Found {$contactFolders->count()} contact folders to process (from new users only)");
            } else {
                // Get all contact folders that have Microsoft Graph IDs
                $contactFolders = ContactFolder::whereNotNull('ms_folder_id')
                                    ->get();

                MicrosoftGraphUtilities::logOperation('info', "Found {$contactFolders->count()} contact folders to process (all users)");
            }

            foreach ($contactFolders as $contactFolder) {
                $stats['folders_processed']++;
                $this->syncContactsForFolder($contactFolder, $stats, $dryRun);
            }

        } catch (Exception $e) {
            $stats['errors'][] = $e->getMessage();
            MicrosoftGraphUtilities::logOperation('error', 'Contacts sync failed', [
                'error' => $e->getMessage()
            ]);
        }

        MicrosoftGraphUtilities::logOperation('success', 'Contacts synchronization completed', $stats);

        // Transform stats to expected format for command
        return [
            'created' => $stats['contacts_created'],
            'updated' => $stats['contacts_updated'],
            'removed' => $stats['contacts_removed'],
            'errors' => count($stats['errors']),
            'failed' => $stats['contacts_failed'],
            'folders_processed' => $stats['folders_processed'],
            'error_details' => $stats['errors']
        ];
    }

    /**
     * Sync contacts for a specific contact folder
     */
    protected function syncContactsForFolder(ContactFolder $contactFolder, array &$stats, bool $dryRun): void
    {
        try {
            $model = $contactFolder->folderable;
            $modelType = $this->getModelTypeFromClass($contactFolder->folderable_type);

            MicrosoftGraphUtilities::logOperation('sync', "Syncing contacts for folder: {$contactFolder->display_name}");

            // Extract folder type from display name (TO or CC)
            $folderType = $this->extractFolderTypeFromDisplayName($contactFolder->display_name);

            // Get users that should be in this folder
            $expectedUsers = $this->getUsersForFolder($model, $modelType, $folderType);
            $expectedUserIds = $expectedUsers->pluck('id')->toArray();

            MicrosoftGraphUtilities::logOperation('info', "Found " . count($expectedUserIds) . " users for folder: {$contactFolder->display_name}");

            // Get existing contacts for this folder
            $existingContacts = Contact::where('contact_folder_id', $contactFolder->id)->get();
            $existingUserIds = $existingContacts->pluck('user_id')->toArray();

            // Users to add (in expected but not in existing)
            $usersToAdd = array_diff($expectedUserIds, $existingUserIds);

            // Users to remove (in existing but not in expected)
            $usersToRemove = array_diff($existingUserIds, $expectedUserIds);

            // Users to potentially update (in both)
            $usersToUpdate = array_intersect($expectedUserIds, $existingUserIds);

            MicrosoftGraphUtilities::logOperation('info', "Folder {$contactFolder->display_name}: Add=" . count($usersToAdd) . ", Remove=" . count($usersToRemove) . ", Update=" . count($usersToUpdate));

            // Add new contacts
            foreach ($usersToAdd as $userId) {
                $user = $expectedUsers->firstWhere('id', $userId);
                if ($user && $this->createContactForUser($user, $contactFolder, $dryRun)) {
                    $stats['contacts_created']++;
                } else {
                    $stats['contacts_failed']++;
                }
            }

            // Remove contacts that shouldn't be there
            foreach ($usersToRemove as $userId) {
                $contact = $existingContacts->firstWhere('user_id', $userId);
                if ($contact && $this->removeContact($contact, $contactFolder, $dryRun)) {
                    $stats['contacts_removed']++;
                } else {
                    $stats['contacts_failed']++;
                }
            }

            // Update existing contacts if needed
            foreach ($usersToUpdate as $userId) {
                $user = $expectedUsers->firstWhere('id', $userId);
                $contact = $existingContacts->firstWhere('user_id', $userId);

                if ($user && $contact && $this->updateContactForUser($user, $contact, $contactFolder, $dryRun)) {
                    $stats['contacts_updated']++;
                }
            }

        } catch (Exception $e) {
            $stats['errors'][] = "Folder {$contactFolder->display_name}: " . $e->getMessage();
            MicrosoftGraphUtilities::logOperation('error', "Failed to sync contacts for folder", [
                'folder_id' => $contactFolder->id,
                'folder_name' => $contactFolder->display_name,
                'error' => $e->getMessage()
            ]);
        }
    }

    /**
     * Create a contact for a user in a specific folder
     */
    public function createContactForUser(User $user, ContactFolder $contactFolder, bool $dryRun): bool
    {
        try {
            if (!MicrosoftGraphUtilities::isValidEmail($user->email)) {
                MicrosoftGraphUtilities::logOperation('warning', "Skipping user without valid email", [
                    'user_id' => $user->id,
                    'user_name' => $user->name,
                    'email' => $user->email
                ]);
                return false;
            }

            $folderType = $this->extractFolderTypeFromDisplayName($contactFolder->display_name);
            $contactData = MicrosoftGraphUtilities::createContactData($user, $folderType);

            MicrosoftGraphUtilities::logOperation('create', "Creating contact: {$contactData['displayName']}");

            if ($dryRun) {
                $msContactId = 'mock_contact_' . $user->id . '_' . $contactFolder->id;
            } else {
                $serviceUserEmail = $contactFolder->microsoftUser->email;

                $response = $this->apiClient->postWithRetry(
                    "users/{$serviceUserEmail}/contactFolders/{$contactFolder->ms_folder_id}/contacts",
                    $contactData,
                    [
                        'operation' => 'create_contact',
                        'user_name' => $user->name,
                        'folder_name' => $contactFolder->display_name
                    ]
                );

                if (!$response) {
                    throw new Exception("Failed to create contact for user: {$user->name}");
                }

                $msContactId = $this->apiClient->getResponseValue($response, 'id');
                if (!$msContactId) {
                    throw new Exception("No contact ID returned from Microsoft Graph");
                }
            }

            // Save contact in database
            Contact::create([
                'contact_folder_id' => $contactFolder->id,
                'ms_contact_id' => $msContactId,
                'display_name' => $contactData['displayName'],
                'email' => $user->email,
                'given_name' => $user->first_name ?? '',
                'surname' => $user->last_name ?? ''
            ]);

            MicrosoftGraphUtilities::logOperation('success', "Created contact: {$contactData['displayName']}");
            return true;

        } catch (Exception $e) {
            MicrosoftGraphUtilities::logOperation('error', "Failed to create contact", [
                'user_id' => $user->id,
                'user_name' => $user->name ?? 'Unknown',
                'folder_id' => $contactFolder->id,
                'error' => $e->getMessage()
            ]);
            return false;
        }
    }

    /**
     * Update a contact for a user
     */
    protected function updateContactForUser(User $user, Contact $contact, ContactFolder $contactFolder, bool $dryRun): bool
    {
        try {
            $folderType = $this->extractFolderTypeFromDisplayName($contactFolder->display_name);
            $contactData = MicrosoftGraphUtilities::createContactData($user, $folderType);
            $currentDisplayName = $contact->display_name;
            $newDisplayName = $contactData['displayName'];

            // Check if update is needed
            if ($currentDisplayName === $newDisplayName && $contact->email === $user->email) {
                return true; // No update needed
            }

            MicrosoftGraphUtilities::logOperation('update', "Updating contact: {$currentDisplayName} → {$newDisplayName}");

            if (!$dryRun) {
                $serviceUserEmail = $contactFolder->microsoftUser->email;

                $response = $this->apiClient->patchWithRetry(
                    "users/{$serviceUserEmail}/contactFolders/{$contactFolder->ms_folder_id}/contacts/{$contact->ms_contact_id}",
                    $contactData,
                    [
                        'operation' => 'update_contact',
                        'user_name' => $user->name,
                        'contact_id' => $contact->ms_contact_id
                    ]
                );

                if (!$response) {
                    throw new Exception("Failed to update contact for user: {$user->name}");
                }
            }

            // Update contact in database
            $contact->update([
                'display_name' => $contactData['displayName'],
                'email' => $user->email
            ]);

            MicrosoftGraphUtilities::logOperation('success', "Updated contact: {$newDisplayName}");
            return true;

        } catch (Exception $e) {
            MicrosoftGraphUtilities::logOperation('error', "Failed to update contact", [
                'contact_id' => $contact->id,
                'user_id' => $user->id,
                'error' => $e->getMessage()
            ]);
            return false;
        }
    }

    /**
     * Remove a contact
     */
    protected function removeContact(Contact $contact, ContactFolder $contactFolder, bool $dryRun): bool
    {
        try {
            MicrosoftGraphUtilities::logOperation('delete', "Removing contact: {$contact->display_name}");

            if (!$dryRun) {
                $serviceUserEmail = $contactFolder->microsoftUser->email;

                $this->apiClient->deleteWithRetry(
                    "users/{$serviceUserEmail}/contactFolders/{$contactFolder->ms_folder_id}/contacts/{$contact->ms_contact_id}",
                    [
                        'operation' => 'delete_contact',
                        'contact_name' => $contact->display_name,
                        'contact_id' => $contact->ms_contact_id
                    ]
                );
            }

            // Remove contact from database
            $contact->delete();

            MicrosoftGraphUtilities::logOperation('success', "Removed contact: {$contact->display_name}");
            return true;

        } catch (Exception $e) {
            MicrosoftGraphUtilities::logOperation('error', "Failed to remove contact", [
                'contact_id' => $contact->id,
                'contact_name' => $contact->display_name,
                'error' => $e->getMessage()
            ]);
            return false;
        }
    }

    /**
     * Extract folder type from display name (TO or CC)
     */
    protected function extractFolderTypeFromDisplayName(string $displayName): string
    {
        return MicrosoftGraphUtilities::extractFolderType($displayName);
    }

    /**
     * Get users that should be in a specific folder
     */
    protected function getUsersForFolder($model, string $modelType, string $folderType)
    {
        MicrosoftGraphUtilities::logOperation('info', "Getting users for {$modelType} folder type: {$folderType}");

        $query = match($modelType) {
            'committee' => $model->users(),
            'workgroup' => $model->users(),
            default => throw new Exception("Unknown model type: {$modelType}")
        };

        // Get all users and then filter by type in PHP to avoid Eloquent pivot issues
        $allUsers = $query->get();

        $filteredUsers = $allUsers->filter(function ($user) use ($folderType, $modelType) {
            $pivotType = strtolower(trim($user->pivot->type ?? ''));

            if ($folderType === 'TO') {
                // Include users with 'to' type or standard member types
                return in_array($pivotType, ['to', 'member', 'chair', 'co-chair', 'vice-chair']);
            } elseif ($folderType === 'CC') {
                // Include users with 'cc' type or standard observer types
                return in_array($pivotType, ['cc', 'observer', 'correspondent']);
            }

            return false;
        });

        MicrosoftGraphUtilities::logOperation('info', "Filtered to " . $filteredUsers->count() . " users for {$folderType} folder");

        return $filteredUsers;
    }

    /**
     * List all contacts in a folder
     */
    public function listContactsInFolder(string $folderId, string $userEmail): array
    {
        try {
            $endpoint = "users/{$userEmail}/contactFolders/{$folderId}/contacts";
            $contacts = $this->apiClient->getAllPaginatedItems($endpoint);

            MicrosoftGraphUtilities::logOperation('debug', 'Raw contacts from API', [
                'folder_id' => $folderId,
                'user_email' => $userEmail,
                'contact_count' => count($contacts),
                'first_contact_sample' => $contacts[0] ?? null
            ]);

            return $contacts;

        } catch (Exception $e) {
            MicrosoftGraphUtilities::logOperation('error', 'Failed to list contacts in folder', [
                'folder_id' => $folderId,
                'user_email' => $userEmail,
                'error' => $e->getMessage()
            ]);
            return [];
        }
    }

    /**
     * Delete all contacts in all folders
     */
    public function deleteAllContacts(bool $dryRun = false): array
    {
        $this->apiClient->setDryRun($dryRun);

        $stats = [
            'contacts_deleted' => 0,
            'errors' => []
        ];

        try {
            $contactFolders = ContactFolder::whereNotNull('ms_folder_id')->get();

            foreach ($contactFolders as $contactFolder) {
                $contacts = $this->listContactsInFolder(
                    $contactFolder->ms_folder_id,
                    $contactFolder->microsoftUser->email
                );

                foreach ($contacts as $contact) {
                    $contactId = $this->apiClient->getResponseValue($contact, 'id');
                    $displayName = $this->apiClient->getResponseValue($contact, 'displayName');

                    if ($this->deleteContactById($contactId, $contactFolder)) {
                        $stats['contacts_deleted']++;
                    } else {
                        $stats['errors'][] = "Failed to delete contact: {$displayName}";
                    }
                }
            }

            // Clean up database records
            if (!$dryRun) {
                $contactsDeleted = Contact::count();
                Contact::query()->delete();
                MicrosoftGraphUtilities::logOperation('info', "Cleaned up contacts table ({$contactsDeleted} records deleted)");
            }

        } catch (Exception $e) {
            $stats['errors'][] = $e->getMessage();
            MicrosoftGraphUtilities::logOperation('error', 'Failed to delete all contacts', [
                'error' => $e->getMessage()
            ]);
        }

        return $stats;
    }

    /**
     * Delete a contact by ID with retry logic
     */
    protected function deleteContactById(string $contactId, ContactFolder $contactFolder): bool
    {
        try {
            $serviceUserEmail = $contactFolder->microsoftUser->email;

            $this->apiClient->deleteWithRetry(
                "users/{$serviceUserEmail}/contactFolders/{$contactFolder->ms_folder_id}/contacts/{$contactId}",
                [
                    'operation' => 'delete_contact_by_id',
                    'contact_id' => $contactId,
                    'folder_id' => $contactFolder->ms_folder_id
                ]
            );

            return true;

        } catch (Exception $e) {
            MicrosoftGraphUtilities::logOperation('error', 'Failed to delete contact by ID', [
                'contact_id' => $contactId,
                'folder_id' => $contactFolder->ms_folder_id,
                'error' => $e->getMessage()
            ]);
            return false;
        }
    }

    /**
     * Get model type from class name
     */
    protected function getModelTypeFromClass(string $className): string
    {
        return match($className) {
            Committee::class => 'committee',
            WorkGroup::class => 'workgroup',
            default => throw new Exception("Unknown model class: {$className}")
        };
    }
}
