<?php

namespace PloverCRM\Modules\Integrations\Services;

use PloverCRM\Core\Container;
use PloverCRM\Core\CRM\CRMProviderInterface;
use PloverCRM\Modules\Integrations\Providers\DataSourceProviderInterface;
use PloverCRM\Modules\Integrations\Providers\ElementorFormProvider;
use PloverCRM\Modules\Integrations\Providers\FluentFormProvider;
use PloverCRM\Modules\Integrations\Providers\ForminatorProvider;
use PloverCRM\Modules\Integrations\Providers\GravityFormsProvider;
use PloverCRM\Modules\Integrations\Providers\MetFormProvider;
use PloverCRM\Modules\Integrations\Providers\NinjaFormsProvider;
use PloverCRM\Modules\Integrations\Providers\WPFormsProvider;
use PloverCRM\Modules\Integrations\Providers\ContactForm7Provider;
use PloverCRM\Modules\Integrations\Providers\FormidableFormsProvider;
use PloverCRM\Modules\Integrations\Providers\SureFormsProvider;
use PloverCRM\Modules\Integrations\Providers\WSFormProvider;
use PloverCRM\Modules\Integrations\Providers\BitFormProvider;
use PloverCRM\Modules\Integrations\Providers\MetaLeadAdsProvider;

/**
 * Data Source Service
 * 
 * Handles all data source integrations (forms, e-commerce, memberships, etc.)
 * CRM-agnostic: works with any CRM provider (PloverCRM, FluentCRM, etc.)
 */
class DataSourceService
{
  private $providers = [];
  private $allProviders = []; // All providers including unavailable ones
  private Container $container;

  private static $categories = [
    'forms' => [
      'key' => 'forms',
      'label' => 'Forms',
      'icon' => 'forms.svg',
      'description' => 'Capture leads from form submissions',
      'provider_label' => 'Form Plugin',
      'source_label' => 'Form',
      'source_placeholder' => 'Select a form...',
    ],
    'ads' => [
      'key' => 'ads',
      'label' => 'Ad Lead Forms',
      'icon' => 'ads.svg',
      'description' => 'Capture leads from ad platforms',
      'provider_label' => 'Platform',
      'source_label' => 'Lead Form',
      'source_placeholder' => 'Select a lead form...',
    ],
    'ecommerce' => [
      'key' => 'ecommerce',
      'label' => 'E-Commerce',
      'icon' => 'ecommerce.svg',
      'description' => 'Sync customers from online stores',
      'provider_label' => 'Store Plugin',
      'source_label' => 'Event',
      'source_placeholder' => 'Select an event...',
    ],
    'webhooks' => [
      'key' => 'webhooks',
      'label' => 'Webhooks',
      'icon' => 'webhooks.svg',
      'description' => 'Receive data from external services',
      'provider_label' => 'Provider',
      'source_label' => 'Webhook',
      'source_placeholder' => 'Select a webhook...',
    ],
  ];

  public function __construct(Container $container)
  {
    $this->container = $container;
    $this->registerProviders();
  }

  /**
   * Get the active CRM provider
   */
  private function getCRM(): CRMProviderInterface
  {
    return $this->container->get('crm');
  }

  private function registerProviders(): void
  {
    $this->registerProvider(new FluentFormProvider());
    $this->registerProvider(new ElementorFormProvider());
    $this->registerProvider(new MetFormProvider());
    $this->registerProvider(new ForminatorProvider());
    $this->registerProvider(new WPFormsProvider());
    $this->registerProvider(new GravityFormsProvider());
    $this->registerProvider(new NinjaFormsProvider());
    $this->registerProvider(new ContactForm7Provider());
    $this->registerProvider(new FormidableFormsProvider());
    $this->registerProvider(new SureFormsProvider());
    $this->registerProvider(new WSFormProvider());
    $this->registerProvider(new BitFormProvider());
    $this->registerProvider(new MetaLeadAdsProvider());
    $this->registerProvider(new \PloverCRM\Modules\Integrations\Providers\GoogleAdsProvider());
    $this->registerProvider(new \PloverCRM\Modules\Integrations\Providers\WebhookProvider());
    do_action('plover_crm/register_data_source_providers', $this);
  }

  public function registerProvider(DataSourceProviderInterface $provider): void
  {
    $category = $provider->getCategory();
    $key = $provider->getKey();

    // Always store provider info for reference (even if unavailable)
    if (!isset($this->allProviders[$category])) {
      $this->allProviders[$category] = [];
    }
    $this->allProviders[$category][$key] = [
      'key' => $key,
      'name' => $provider->getName(),
      'icon' => $provider->getIcon(),
      'available' => $provider->isAvailable(),
    ];

    // Only register active providers
    if (!$provider->isAvailable()) {
      return;
    }

    if (!isset($this->providers[$category])) {
      $this->providers[$category] = [];
    }

    $this->providers[$category][$key] = $provider;
  }

  /**
   * Get the URL for a provider icon
   */
  public function getIconUrl(string $iconFile): string
  {
    $baseUrl = defined('PLOVER_CRM_PLUGIN_URL') ? PLOVER_CRM_PLUGIN_URL : '';
    return $baseUrl . 'app/Modules/Integrations/assets/icons/' . $iconFile;
  }

  public function getCategories(): array
  {
    $result = [];

    foreach (self::$categories as $key => $category) {
      $categoryProviders = $this->providers[$key] ?? [];

      $result[] = [
        'key' => $category['key'],
        'label' => $category['label'],
        'icon' => $this->getIconUrl($category['icon']),
        'description' => $category['description'],
        'provider_label' => $category['provider_label'],
        'source_label' => $category['source_label'],
        'source_placeholder' => $category['source_placeholder'],
        'providers' => array_map(fn($p) => [
          'key' => $p->getKey(),
          'name' => $p->getName(),
          'icon' => $this->getIconUrl($p->getIcon()),
        ], array_values($categoryProviders)),
        'has_providers' => !empty($categoryProviders),
      ];
    }

    return $result;
  }

  public function getActiveCategories(): array
  {
    return array_filter($this->getCategories(), fn($cat) => $cat['has_providers']);
  }

  public function getProvidersByCategory(string $category): array
  {
    $providers = $this->providers[$category] ?? [];
    return array_map(fn($p) => [
      'key' => $p->getKey(),
      'name' => $p->getName(),
      'icon' => $this->getIconUrl($p->getIcon()),
    ], array_values($providers));
  }

  public function getProvider(string $providerKey): ?DataSourceProviderInterface
  {
    foreach ($this->providers as $providers) {
      if (isset($providers[$providerKey])) {
        return $providers[$providerKey];
      }
    }
    return null;
  }

  /**
   * Get provider info even if the provider is unavailable
   * Returns array with key, name, icon, available
   */
  public function getProviderInfo(string $providerKey): ?array
  {
    foreach ($this->allProviders as $providers) {
      if (isset($providers[$providerKey])) {
        $info = $providers[$providerKey];
        $info['icon_url'] = $this->getIconUrl($info['icon']);
        return $info;
      }
    }
    return null;
  }

  /**
   * Get all providers by category including unavailable ones
   * Active (available) providers are sorted to the top
   */
  public function getAllProvidersByCategory(string $category): array
  {
    $providers = $this->allProviders[$category] ?? [];
    $result = array_map(function ($p) {
      return [
        'key' => $p['key'],
        'name' => $p['name'],
        'icon' => $this->getIconUrl($p['icon']),
        'available' => $p['available'],
      ];
    }, array_values($providers));

    // Sort: available providers first, then alphabetically by name
    usort($result, function ($a, $b) {
      if ($a['available'] !== $b['available']) {
        return $b['available'] <=> $a['available']; // Available first
      }
      return strcasecmp($a['name'], $b['name']); // Then alphabetically
    });

    return $result;
  }

  public function registerListeners(): void
  {
    foreach ($this->providers as $providers) {
      foreach ($providers as $provider) {
        $provider->registerEventListener([$this, 'handleEvent']);
      }
    }
  }

  public function handleEvent(string $providerKey, string $sourceId, array $data): void
  {
    $integrations = $this->getIntegrationsForSource($providerKey, $sourceId);

    if (empty($integrations)) {
      // Debug: log when no integration found
      $logger = $this->getLogger('ads');
      $logger->info('No integration found for source', [
        'provider' => $providerKey,
        'source_id' => $sourceId,
        'data_keys' => array_keys($data),
      ]);
      return;
    }

    $crm = $this->getCRM();

    foreach ($integrations as $integration) {
      $this->processIntegration($integration, $data, $crm);
    }
  }

  private function processIntegration(object $integration, array $data, CRMProviderInterface $crm): void
  {
    $startTime = microtime(true);
    $category = $integration->category ?? 'forms';

    // Get appropriate logger based on category
    $logger = $this->getLogger($category);

    if (!$this->checkConditions($integration, $data)) {
      $logger->info('Integration conditions not met, skipping', [
        'integration_id' => $integration->id,
        'provider' => $integration->provider,
        'source_id' => $integration->source_id,
      ]);
      return;
    }

    $contactData = $this->mapFields($integration, $data);

    if (empty($contactData)) {
      $logger->logProcessingError(
        $integration->provider,
        $integration->source_id,
        $integration->id,
        'Field mapping resulted in empty contact data',
        ['raw_data' => $data, 'stage' => 'mapping']
      );
      return;
    }

    // Get mappings for unmapped fields check
    $mappings = $this->normalizeFieldMappings(
      json_decode($integration->field_mappings, true) ?: []
    );

    // Check for unmapped fields
    $this->checkUnmappedFields($integration, $data, $mappings, $logger);

    $validation = $crm->validateContactData($contactData);

    if (!$validation['valid']) {
      $logger->logProcessingError(
        $integration->provider,
        $integration->source_id,
        $integration->id,
        $validation['error_message'] ?? 'Validation failed',
        ['raw_data' => $data, 'mapped_data' => $contactData, 'stage' => 'validation']
      );
      return;
    }



    $lists = json_decode($integration->lists, true) ?: [];
    if (!empty($lists)) {
      $contactData['lists'] = array_map('intval', $lists);
    }

    $tags = json_decode($integration->tags, true) ?: [];
    if (!empty($tags)) {
      $contactData['tags'] = array_map('intval', $tags);
    }

    // Set a normalized contact source so enrichment can consistently detect
    // form-based and webhook-based touches across providers.
    if ($category === 'forms') {
      $contactData['source'] = 'Form';
    } elseif ($category === 'ads' || $category === 'webhooks') {
      $contactData['source'] = 'Webhook';
    }

    try {
      $result = $crm->createOrUpdateContact($contactData);

      if ($result) {
        $executionTime = (int) ((microtime(true) - $startTime) * 1000);
        $isUpdate = empty($result['is_new']);

        if ($isUpdate) {
          $logger->logContactUpdated(
            $integration->provider,
            $integration->source_id,
            $integration->id,
            $result['id'],
            $result['email'] ?? '',
            $contactData,
            $executionTime
          );
        } else {
          $logger->logContactCreated(
            $integration->provider,
            $integration->source_id,
            $integration->id,
            $result['id'],
            $result['email'] ?? '',
            $contactData,
            $executionTime
          );
        }

        do_action('plover_crm/integration_contact_processed', $result, $integration, $data);
      } else {
        $logger->logProcessingError(
          $integration->provider,
          $integration->source_id,
          $integration->id,
          'CRM returned null result',
          ['raw_data' => $data, 'mapped_data' => $contactData, 'stage' => 'crm_create']
        );
      }
    } catch (\Exception $e) {
      $logger->logProcessingError(
        $integration->provider,
        $integration->source_id,
        $integration->id,
        $e->getMessage(),
        ['raw_data' => $data, 'mapped_data' => $contactData, 'stage' => 'crm_exception']
      );
    }
  }

  /**
   * Get logger based on category
   */
  private function getLogger(string $category)
  {
    if ($category === 'ads') {
      return new AdsLogger();
    }
    if ($category === 'webhooks') {
      return new WebhooksLogger();
    }
    return new FormsLogger();
  }

  /**
   * Get source name for logging
   */
  private function getSourceName(?DataSourceProviderInterface $provider, string $sourceId): string
  {
    if (!$provider) {
      return $sourceId;
    }

    try {
      $sources = $provider->getSources();
      foreach ($sources as $source) {
        if ($source['id'] === $sourceId) {
          return $source['title'] ?? $sourceId;
        }
      }
    } catch (\Exception $e) {
      // Ignore errors
    }

    return $sourceId;
  }

  private function mapFields(object $integration, array $data): array
  {
    $mappings = $this->normalizeFieldMappings(
      json_decode($integration->field_mappings, true) ?: []
    );
    $contactData = [];
    $customFields = [];

    foreach ($mappings as $mapping) {
      $sourceField = $mapping['source_field'] ?? '';
      $crmField = $mapping['crm_field'] ?? '';

      if (empty($sourceField) || empty($crmField)) {
        continue;
      }

      $value = $this->getNestedValue($data, $sourceField);

      if ($value === null || $value === '') {
        continue;
      }

      if (strpos($crmField, 'cf_') === 0) {
        $customFields[substr($crmField, 3)] = $value;
      } else {
        $contactData[$crmField] = $value;
      }
    }

    if (!empty($customFields)) {
      $contactData['custom_fields'] = $customFields;
    }

    return $contactData;
  }

  private function getNestedValue(array $data, string $key)
  {
    // First, check if the key exists directly (for flat keys like "1.first" or "3")
    if (array_key_exists($key, $data)) {
      return $data[$key];
    }

    // Then try nested access (for keys like "name.first" where $data['name']['first'] exists)
    $keys = explode('.', $key);
    $value = $data;

    foreach ($keys as $k) {
      if (!is_array($value) || !array_key_exists($k, $value)) {
        return null;
      }
      $value = $value[$k];
    }

    return $value;
  }

  private function checkConditions(object $integration, array $data): bool
  {
    $conditions = json_decode($integration->conditions, true) ?: [];

    if (empty($conditions) || empty($conditions['rules'])) {
      return true;
    }

    $rules = $conditions['rules'];
    $logic = $conditions['logic'] ?? 'all';

    $results = array_map(fn($rule) => $this->evaluateCondition($rule, $data), $rules);

    return $logic === 'all'
      ? !in_array(false, $results, true)
      : in_array(true, $results, true);
  }

  private function evaluateCondition(array $rule, array $data): bool
  {
    $fieldValue = $this->getNestedValue($data, $rule['field'] ?? '');
    $value = $rule['value'] ?? '';

    return match ($rule['operator'] ?? 'equals') {
      'equals' => $fieldValue == $value,
      'not_equals' => $fieldValue != $value,
      'contains' => is_string($fieldValue) && str_contains($fieldValue, $value),
      'not_contains' => is_string($fieldValue) && !str_contains($fieldValue, $value),
      'is_empty' => empty($fieldValue),
      'is_not_empty' => !empty($fieldValue),
      'greater_than' => is_numeric($fieldValue) && $fieldValue > $value,
      'less_than' => is_numeric($fieldValue) && $fieldValue < $value,
      default => true,
    };
  }

  public function getIntegrationsForSource(string $providerKey, string $sourceId): array
  {
    global $wpdb;
    $table = $wpdb->prefix . 'plover_crm_data_sources';

    return $wpdb->get_results($wpdb->prepare(
      "SELECT * FROM $table WHERE provider = %s AND source_id = %s AND status = 'active'",
      $providerKey,
      $sourceId
    ));
  }

  public function getAllIntegrationsForSource(string $providerKey, string $sourceId, ?int $excludeId = null): array
  {
    global $wpdb;
    $table = $wpdb->prefix . 'plover_crm_data_sources';

    $query = "SELECT * FROM $table WHERE provider = %s AND source_id = %s";
    $params = [$providerKey, $sourceId];

    if ($excludeId !== null && $excludeId > 0) {
      $query .= " AND id != %d";
      $params[] = $excludeId;
    }

    $query .= " ORDER BY updated_at DESC, id DESC";

    return $wpdb->get_results($wpdb->prepare($query, ...$params)) ?: [];
  }

  public function getAllIntegrations(array $args = []): array
  {
    global $wpdb;
    $table = $wpdb->prefix . 'plover_crm_data_sources';

    $page = $args['page'] ?? 1;
    $per_page = $args['per_page'] ?? 20;
    $status = $args['status'] ?? null;
    $provider = $args['provider'] ?? null;
    $category = $args['category'] ?? null;

    $where = [];
    $params = [];

    if ($status) {
      $where[] = 'status = %s';
      $params[] = $status;
    }

    if ($provider) {
      $where[] = 'provider = %s';
      $params[] = $provider;
    }

    if ($category) {
      $where[] = 'category = %s';
      $params[] = $category;
    }

    $where_clause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
    $offset = ($page - 1) * $per_page;

    $query = "SELECT * FROM $table $where_clause ORDER BY created_at DESC LIMIT %d OFFSET %d";
    $params[] = $per_page;
    $params[] = $offset;

    $integrations = !empty($params)
      ? $wpdb->get_results($wpdb->prepare($query, ...$params))
      : $wpdb->get_results($query);

    $count_query = "SELECT COUNT(*) FROM $table $where_clause";
    $count_params = array_slice($params, 0, -2);
    $total = !empty($count_params)
      ? $wpdb->get_var($wpdb->prepare($count_query, ...$count_params))
      : $wpdb->get_var($count_query);

    return [
      'integrations' => $integrations ?: [],
      'total' => (int) $total,
      'page' => $page,
      'per_page' => $per_page,
      'total_pages' => ceil($total / $per_page),
    ];
  }

  public function getIntegration(int $id): ?object
  {
    global $wpdb;
    $table = $wpdb->prefix . 'plover_crm_data_sources';

    return $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $id));
  }

  public function createIntegration(array $data)
  {
    global $wpdb;
    $table = $wpdb->prefix . 'plover_crm_data_sources';

    $provider = $this->getProvider($data['provider'] ?? '');
    $category = $provider ? $provider->getCategory() : 'forms';

    $result = $wpdb->insert($table, [
      'name' => sanitize_text_field($data['name']),
      'category' => $category,
      'provider' => sanitize_text_field($data['provider']),
      'source_id' => sanitize_text_field($data['source_id']),
      'event_type' => sanitize_text_field($data['event_type'] ?? 'default'),
      'field_mappings' => wp_json_encode(
        $this->normalizeFieldMappings($data['field_mappings'] ?? [])
      ),
      'lists' => wp_json_encode($data['lists'] ?? []),
      'tags' => wp_json_encode($data['tags'] ?? []),
      'options' => wp_json_encode($data['options'] ?? []),
      'conditions' => wp_json_encode($data['conditions'] ?? []),
      'status' => sanitize_text_field($data['status'] ?? 'active'),
    ]);

    return $result ? $wpdb->insert_id : false;
  }

  public function updateIntegration(int $id, array $data): bool
  {
    global $wpdb;
    $table = $wpdb->prefix . 'plover_crm_data_sources';

    $update = [];
    $format = [];

    foreach (['name', 'provider', 'source_id', 'event_type', 'status'] as $field) {
      if (isset($data[$field])) {
        $update[$field] = sanitize_text_field($data[$field]);
        $format[] = '%s';
      }
    }

    foreach (['field_mappings', 'lists', 'tags', 'options', 'conditions'] as $field) {
      if (isset($data[$field])) {
        $value = $field === 'field_mappings'
          ? $this->normalizeFieldMappings($data[$field])
          : $data[$field];
        $update[$field] = wp_json_encode($value);
        $format[] = '%s';
      }
    }

    if (empty($update)) {
      return false;
    }

    return $wpdb->update($table, $update, ['id' => $id], $format, ['%d']) !== false;
  }

  public function syncWebhookIntegrationName(string $sourceId, string $name): bool
  {
    global $wpdb;
    $table = $wpdb->prefix . 'plover_crm_data_sources';

    return $wpdb->update(
      $table,
      ['name' => sanitize_text_field($name)],
      [
        'provider' => 'webhook',
        'source_id' => sanitize_text_field($sourceId),
      ],
      ['%s'],
      ['%s', '%s']
    ) !== false;
  }

  public function deleteIntegration(int $id): bool
  {
    global $wpdb;
    $table = $wpdb->prefix . 'plover_crm_data_sources';

    return $wpdb->delete($table, ['id' => $id], ['%d']) !== false;
  }

  public function deleteIntegrationsForSource(string $providerKey, string $sourceId): bool
  {
    global $wpdb;
    $table = $wpdb->prefix . 'plover_crm_data_sources';

    return $wpdb->delete(
      $table,
      [
        'provider' => sanitize_text_field($providerKey),
        'source_id' => sanitize_text_field($sourceId),
      ],
      ['%s', '%s']
    ) !== false;
  }

  /**
   * Check for unmapped fields in submission data
   * 
   * Detects fields present in the data but not mapped in the integration.
   * Logs a warning if unmapped fields are found.
   *
   * @param object $integration Integration object
   * @param array $data Submission data
   * @param array $mappings Field mappings
   * @param object $logger Logger instance (FormsLogger or AdsLogger)
   */
  private function checkUnmappedFields(object $integration, array $data, array $mappings, $logger): void
  {
    $mappings = $this->normalizeFieldMappings($mappings);

    // Get all mapped source fields
    $mappedFields = array_column($mappings, 'source_field');

    // Get all fields in the data (excluding nested arrays/objects)
    $dataFields = [];
    foreach ($data as $key => $value) {
      if (!is_array($value) && !is_object($value)) {
        $dataFields[] = $key;
      }
    }

    // Find unmapped fields
    $unmappedFields = array_diff($dataFields, $mappedFields);

    // Filter out empty values and metadata/system fields
    $unmappedFields = array_filter($unmappedFields, function ($field) use ($data) {
      $value = $data[$field] ?? '';
      // Ignore empty values
      if (empty($value)) {
        return false;
      }
      // Ignore metadata fields (start with _)
      if (strpos($field, '_') === 0) {
        return false;
      }
      // Ignore common system fields
      $ignoreFields = ['_wp_http_referer', 'action', 'nonce', '_wpnonce', 'submit'];
      return !in_array($field, $ignoreFields);
    });

    // Log if unmapped fields found
    if (!empty($unmappedFields)) {
      $logger->logUnmappedFields(
        $integration->provider,
        $integration->source_id,
        $integration->id,
        array_values($unmappedFields),
        $data
      );
    }
  }

  private function normalizeFieldMappings(array $mappings): array
  {
    $normalized = [];

    foreach ($mappings as $mapping) {
      if (!is_array($mapping)) {
        continue;
      }

      $normalized[] = [
        'source_field' => (string) ($mapping['source_field'] ?? $mapping['sourceField'] ?? ''),
        'crm_field' => (string) ($mapping['crm_field'] ?? $mapping['crmField'] ?? ''),
      ];
    }

    return $normalized;
  }
}
