<?php

namespace ModernFields\ImportExport\Services;

defined('ABSPATH') || exit;

/**
 * Imports field groups, CPT definitions, taxonomy definitions and block
 * definitions from a previously exported JSON payload.
 *
 * Each item is matched by its mf_uuid meta:
 * - No match → create.
 * - Match found, but local version is newer than export date → conflict.
 * - Match found, no conflict (or UUID in $force_uuids) → update.
 *
 * @since 0.8
 */
class ImportService
{
  /**
   * Processes a parsed export payload and returns an import report.
   *
   * @since 0.8
   * @param array    $data        Parsed JSON export data.
   * @param string[] $force_uuids UUIDs whose conflicts should be ignored.
   * @return array{ created: array, updated: array, conflicts: array, errors: array }
   */
  public function import(array $data, array $force_uuids = []): array
  {
    $report = [
      'created'   => [],
      'updated'   => [],
      'conflicts' => [],
      'errors'    => [],
    ];

    $exported_at = $data['exported_at'] ?? null;

    foreach ($data['groups'] ?? [] as $group) {
      $this->importGroup($group, $exported_at, $force_uuids, $report);
    }

    foreach ($data['post_types'] ?? [] as $post_type) {
      $this->importDefinition(
        $post_type,
        MODERN_FIELDS_POST_TYPES_CPT_SLUG,
        'post_type',
        $exported_at,
        $force_uuids,
        $report
      );
    }

    foreach ($data['taxonomies'] ?? [] as $taxonomy) {
      $this->importDefinition(
        $taxonomy,
        MODERN_FIELDS_TAXONOMIES_CPT_SLUG,
        'taxonomy',
        $exported_at,
        $force_uuids,
        $report
      );
    }

    foreach ($data['blocks'] ?? [] as $block) {
      $this->importDefinition(
        $block,
        MODERN_FIELDS_BLOCKS_CPT_SLUG,
        'block',
        $exported_at,
        $force_uuids,
        $report
      );
    }

    return $report;
  }

  /**
   * Imports a single field group.
   *
   * @since 0.8
   * @param array    $data        Serialized group data.
   * @param ?string  $exported_at ISO-8601 export timestamp.
   * @param string[] $force_uuids UUIDs to force-update.
   * @param array    $report      Import report (passed by reference).
   * @return void
   */
  protected function importGroup(array $data, ?string $exported_at, array $force_uuids, array &$report): void
  {
    $uuid   = sanitize_text_field($data['uuid'] ?? '');
    $title  = sanitize_text_field($data['title'] ?? '');
    $status = $this->sanitizePostStatus($data['status'] ?? 'publish');

    if (empty($uuid)) {
      $report['errors'][] = ['type' => 'group', 'title' => $title, 'reason' => 'missing_uuid'];
      return;
    }

    $existing_id = $this->findPostByUuid($uuid, MODERN_FIELDS_CPT_SLUG);

    if ($existing_id) {
      if (!in_array($uuid, $force_uuids, true) && $this->isConflict($existing_id, $exported_at)) {
        $report['conflicts'][] = [
          'type'           => 'group',
          'uuid'           => $uuid,
          'title'          => $title,
          'local_modified' => get_post_modified_time('c', true, $existing_id),
          'export_date'    => $exported_at,
        ];
        return;
      }

      wp_update_post([
        'ID'           => $existing_id,
        'post_title'   => $title,
        'post_excerpt' => sanitize_textarea_field($data['description'] ?? ''),
        'post_content' => wp_kses_post($data['content'] ?? ''),
        'post_status'  => $status,
      ]);

      $this->updateGroupMeta($existing_id, $data['meta'] ?? []);
      $report['updated'][] = ['type' => 'group', 'uuid' => $uuid, 'title' => $title, 'id' => $existing_id];

      return;
    }

    # Create new group
    $post_id = wp_insert_post([
      'post_type'    => MODERN_FIELDS_CPT_SLUG,
      'post_title'   => $title,
      'post_excerpt' => sanitize_textarea_field($data['description'] ?? ''),
      'post_content' => wp_kses_post($data['content'] ?? ''),
      'post_status'  => $status,
    ]);

    if (is_wp_error($post_id)) {
      $report['errors'][] = ['type' => 'group', 'uuid' => $uuid, 'title' => $title, 'reason' => $post_id->get_error_message()];
      return;
    }

    update_post_meta($post_id, 'mf_uuid', $uuid);
    $this->updateGroupMeta($post_id, $data['meta'] ?? []);
    $report['created'][] = ['type' => 'group', 'uuid' => $uuid, 'title' => $title, 'id' => $post_id];
  }

  /**
   * Writes all location/scope/target/role/settings meta for a group.
   *
   * @since 0.8
   * @param int   $post_id
   * @param array $meta
   * @return void
   */
  protected function updateGroupMeta(int $post_id, array $meta): void
  {
    foreach (['mf_location', 'mf_scope', 'mf_cpt', 'mf_position'] as $key) {
      if (array_key_exists($key, $meta)) {
        update_post_meta($post_id, $key, sanitize_text_field((string) $meta[$key]));
      }
    }

    if (isset($meta['mf_targets']) && is_array($meta['mf_targets'])) {
      delete_post_meta($post_id, 'mf_targets');
      foreach ($meta['mf_targets'] as $target) {
        add_post_meta($post_id, 'mf_targets', sanitize_text_field($target));
      }
    }

    if (isset($meta['mf_roles']) && is_array($meta['mf_roles'])) {
      delete_post_meta($post_id, 'mf_roles');
      foreach ($meta['mf_roles'] as $role) {
        add_post_meta($post_id, 'mf_roles', sanitize_text_field($role));
      }
    }

    if (isset($meta['mf_settings']) && is_array($meta['mf_settings'])) {
      update_post_meta($post_id, 'mf_settings', $meta['mf_settings']);
    }
  }

  /**
   * Imports a single CPT or taxonomy definition.
   *
   * @since 0.8
   * @param array    $data        Serialized definition data.
   * @param string   $cpt_slug    Storage post type slug.
   * @param string   $type        Human-readable type label for the report.
   * @param ?string  $exported_at ISO-8601 export timestamp.
   * @param string[] $force_uuids UUIDs to force-update.
   * @param array    $report      Import report (passed by reference).
   * @return void
   */
  protected function importDefinition(
    array $data,
    string $cpt_slug,
    string $type,
    ?string $exported_at,
    array $force_uuids,
    array &$report
  ): void {
    $uuid  = sanitize_text_field($data['uuid'] ?? '');
    $title = sanitize_text_field($data['title'] ?? '');

    if (empty($uuid)) {
      $report['errors'][] = ['type' => $type, 'title' => $title, 'reason' => 'missing_uuid'];
      return;
    }

    $existing_id = $this->findPostByUuid($uuid, $cpt_slug);

    if ($existing_id) {
      if (!in_array($uuid, $force_uuids, true) && $this->isConflict($existing_id, $exported_at)) {
        $report['conflicts'][] = [
          'type'           => $type,
          'uuid'           => $uuid,
          'title'          => $title,
          'local_modified' => get_post_modified_time('c', true, $existing_id),
          'export_date'    => $exported_at,
        ];
        return;
      }

      wp_update_post([
        'ID'           => $existing_id,
        'post_title'   => $title,
        'post_content' => wp_json_encode($data['config'] ?? []),
      ]);

      $report['updated'][] = ['type' => $type, 'uuid' => $uuid, 'title' => $title, 'id' => $existing_id];

      return;
    }

    # Create new definition
    $post_id = wp_insert_post([
      'post_type'    => $cpt_slug,
      'post_title'   => $title,
      'post_content' => wp_json_encode($data['config'] ?? []),
      'post_status'  => 'publish',
    ]);

    if (is_wp_error($post_id)) {
      $report['errors'][] = ['type' => $type, 'uuid' => $uuid, 'title' => $title, 'reason' => $post_id->get_error_message()];
      return;
    }

    update_post_meta($post_id, 'mf_uuid', $uuid);
    $report['created'][] = ['type' => $type, 'uuid' => $uuid, 'title' => $title, 'id' => $post_id];
  }

  /**
   * Returns a validated post status, falling back to 'publish' for unknown values.
   *
   * @since 0.7.3
   * @param string $status
   * @return string
   */
  protected function sanitizePostStatus(string $status): string
  {
    $allowed = ['publish', 'draft'];
    return in_array($status, $allowed, true) ? $status : 'publish';
  }

  /**
   * Finds a post of the given type by its mf_uuid meta.
   *
   * @since 0.8
   * @param string $uuid
   * @param string $post_type
   * @return int|null Post ID if found, null otherwise.
   */
  protected function findPostByUuid(string $uuid, string $post_type): ?int
  {
    $posts = get_posts([
      'post_type'      => $post_type,
      'post_status'    => 'any',
      'posts_per_page' => 1,
      'meta_key'       => 'mf_uuid',
      'meta_value'     => $uuid,
      'fields'         => 'ids',
    ]);

    return !empty($posts) ? (int) $posts[0] : null;
  }

  /**
   * Returns true if the local post was modified more recently than the export.
   *
   * This means the data in the file is stale relative to what is on the server.
   *
   * @since 0.8
   * @param int     $post_id
   * @param ?string $exported_at ISO-8601 timestamp from the export file.
   * @return bool
   */
  protected function isConflict(int $post_id, ?string $exported_at): bool
  {
    if (!$exported_at) {
      return false;
    }

    $local_modified = (int) get_post_modified_time('U', true, $post_id);
    $export_time    = (int) strtotime($exported_at);

    return $local_modified > $export_time;
  }
}
