<?php

/**
 * Full data importer
 *
 * Applies decisions against a normalized ImportManifest produced by
 * ImportParser. Two phases:
 *
 * - `analyze_collisions()` — enriches each course/module/slide/template with
 *   an `existing_match` entry pointing to a title-matching target (or null).
 * - `execute()` — creates, updates, or skips items per the decisions map.
 *
 * Memory variables are never imported (archival-only section); the parser
 * still carries them for fidelity.
 *
 * @package SlideMind\Core
 * @since   0.6.0
 */
namespace SlideMind\Core;

if ( !defined( 'ABSPATH' ) ) {
    exit;
}
class FullImporter {
    /** @var ConfigRepository */
    private $config_repository;

    /** @var TemplateManager */
    private $template_manager;

    public function __construct( ConfigRepository $config_repository, TemplateManager $template_manager ) {
        $this->config_repository = $config_repository;
        $this->template_manager = $template_manager;
    }

    /**
     * Enrich each titled section with `existing_match` keyed by post_id / slug.
     *
     * Match key for posts: lowercased trimmed title within the section.
     * Match key for templates: slug first, fallback to title.
     */
    public function analyze_collisions( ImportManifest $manifest ) : ImportManifest {
        $this->enrich_with_post_match( $manifest->courses, $this->existing_titles_by_section( 'courses' ) );
        $this->enrich_with_post_match( $manifest->modules, $this->existing_titles_by_section( 'modules' ) );
        $this->enrich_with_post_match( $manifest->slides, $this->existing_titles_by_section( 'slides' ) );
        $existing_templates = $this->existing_templates_by_slug_and_title();
        foreach ( $manifest->templates as &$tpl ) {
            $slug = ( isset( $tpl['slug'] ) ? strtolower( trim( (string) $tpl['slug'] ) ) : '' );
            $title = ( isset( $tpl['title'] ) ? strtolower( trim( (string) $tpl['title'] ) ) : '' );
            $match = null;
            if ( $slug !== '' && isset( $existing_templates['by_slug'][$slug] ) ) {
                $match = $existing_templates['by_slug'][$slug];
            } elseif ( $title !== '' && isset( $existing_templates['by_title'][$title] ) ) {
                $match = $existing_templates['by_title'][$title];
            }
            $tpl['existing_match'] = $match;
        }
        unset($tpl);
        return $manifest;
    }

    /**
     * @param array<int, array<string, mixed>> $items    Reference — updated in place.
     * @param array<string, array<string, mixed>> $titles Lowercased-trimmed-title => target summary.
     */
    private function enrich_with_post_match( array &$items, array $titles ) : void {
        foreach ( $items as &$item ) {
            $title = strtolower( trim( (string) ($item['title'] ?? '') ) );
            $item['existing_match'] = ( $title !== '' && isset( $titles[$title] ) ? $titles[$title] : null );
        }
        unset($item);
    }

    /**
     * @return array<string, array{post_id:int,title:string,status:string}>
     */
    private function existing_titles_by_section( string $section ) : array {
        switch ( $section ) {
            case 'courses':
                $items = $this->config_repository->get_courses( 'any' );
                break;
            case 'modules':
                $items = $this->config_repository->get_all_modules( 'any' );
                break;
            case 'slides':
                $items = $this->config_repository->get_all_slides( 'any' );
                break;
            default:
                return [];
        }
        $index = [];
        foreach ( $items as $item ) {
            $title = strtolower( trim( (string) ($item['title'] ?? '') ) );
            if ( $title === '' ) {
                continue;
            }
            // First match wins on duplicate titles in the DB.
            if ( !isset( $index[$title] ) ) {
                $index[$title] = [
                    'post_id' => (int) $item['id'],
                    'title'   => (string) $item['title'],
                    'status'  => (string) ($item['status'] ?? 'publish'),
                ];
            }
        }
        return $index;
    }

    /**
     * @return array{by_slug:array<string,array>,by_title:array<string,array>}
     */
    private function existing_templates_by_slug_and_title() : array {
        $by_slug = [];
        $by_title = [];
        foreach ( $this->template_manager->get_all() as $tpl ) {
            if ( !empty( $tpl['is_default'] ) ) {
                continue;
            }
            $summary = [
                'slug'  => (string) ($tpl['slug'] ?? ''),
                'title' => (string) ($tpl['title'] ?? ''),
            ];
            $slug_key = strtolower( trim( (string) ($tpl['slug'] ?? '') ) );
            $title_key = strtolower( trim( (string) ($tpl['title'] ?? '') ) );
            if ( $slug_key !== '' ) {
                $by_slug[$slug_key] = $summary;
            }
            if ( $title_key !== '' ) {
                $by_title[$title_key] = $summary;
            }
        }
        return [
            'by_slug'  => $by_slug,
            'by_title' => $by_title,
        ];
    }

    /**
     * Execute the import.
     *
     * @param ImportManifest $manifest
     * @param array<string, mixed> $decisions
     * @return array<string, mixed> ImportReport
     */
    public function execute( ImportManifest $manifest, array $decisions ) : array {
        $report = [
            'created'                     => [],
            'overwritten'                 => [],
            'skipped'                     => [],
            'holders'                     => [
                'courses' => [],
                'modules' => [],
            ],
            'memory_variables_skipped'    => count( $manifest->memory_variables ),
            'memory_definitions_imported' => 0,
            'settings_applied'            => false,
            'errors'                      => [],
        ];
        $ref_to_post_id = [];
        if ( isset( $decisions['seeded_refs'] ) && is_array( $decisions['seeded_refs'] ) ) {
            foreach ( $decisions['seeded_refs'] as $ref => $post_id ) {
                if ( is_string( $ref ) && (int) $post_id > 0 ) {
                    $ref_to_post_id[$ref] = (int) $post_id;
                }
            }
        }
        $course_decisions = ( isset( $decisions['decisions']['courses'] ) && is_array( $decisions['decisions']['courses'] ) ? $decisions['decisions']['courses'] : [] );
        $this->apply_courses(
            $manifest->courses,
            $course_decisions,
            $ref_to_post_id,
            $report
        );
        $module_decisions = ( isset( $decisions['decisions']['modules'] ) && is_array( $decisions['decisions']['modules'] ) ? $decisions['decisions']['modules'] : [] );
        $holder_course_id = 0;
        // Lazy — created on first orphan module
        $this->apply_modules(
            $manifest->modules,
            $module_decisions,
            $ref_to_post_id,
            $holder_course_id,
            $report
        );
        $slide_decisions = ( isset( $decisions['decisions']['slides'] ) && is_array( $decisions['decisions']['slides'] ) ? $decisions['decisions']['slides'] : [] );
        $holder_module_id = 0;
        $this->apply_slides(
            $manifest->slides,
            $slide_decisions,
            $ref_to_post_id,
            $holder_course_id,
            $holder_module_id,
            $report
        );
        $template_decisions = ( isset( $decisions['decisions']['templates'] ) && is_array( $decisions['decisions']['templates'] ) ? $decisions['decisions']['templates'] : [] );
        $this->apply_templates( $manifest->templates, $template_decisions, $report );
        $selected_memdef_ids = ( isset( $decisions['selected_memory_definitions'] ) && is_array( $decisions['selected_memory_definitions'] ) ? array_map( 'strval', $decisions['selected_memory_definitions'] ) : [] );
        $report['memory_definitions_imported'] = $this->apply_memory_definitions( $manifest->memory_definitions, $selected_memdef_ids );
        if ( !empty( $decisions['apply_settings'] ) && !empty( $manifest->settings ) ) {
            $report['settings_applied'] = $this->apply_settings( $manifest->settings );
        }
        return $report;
    }

    /**
     * @param array<int, array<string,mixed>>  $courses
     * @param array<string, string>            $decisions  file_ref => action
     * @param array<string, int>               $ref_to_post_id  Reference — updated.
     * @param array<string, mixed>             $report          Reference — updated.
     */
    private function apply_courses(
        array $courses,
        array $decisions,
        array &$ref_to_post_id,
        array &$report
    ) : void {
        foreach ( $courses as $course ) {
            $ref = (string) $course['file_ref'];
            $action = ( isset( $decisions[$ref] ) ? (string) $decisions[$ref] : null );
            if ( $action === null || $action === 'skip' ) {
                // Unselected or explicitly skipped. Record if it had a match, else silent.
                if ( !empty( $course['existing_match'] ) ) {
                    $report['skipped'][] = [
                        'type'   => 'course',
                        'title'  => (string) $course['title'],
                        'reason' => 'user_skip',
                    ];
                }
                continue;
            }
            if ( $action === 'overwrite' && !empty( $course['existing_match']['post_id'] ) ) {
                $post_id = (int) $course['existing_match']['post_id'];
                $result = $this->config_repository->update_course( $post_id, [
                    'title'      => (string) $course['title'],
                    'theme'      => (string) $course['theme'],
                    'objectives' => (string) $course['objectives'],
                    'content'    => (string) $course['content'],
                ] );
                if ( is_wp_error( $result ) ) {
                    $report['errors'][] = $result->get_error_message();
                    continue;
                }
                $ref_to_post_id[$ref] = $post_id;
                $report['overwritten'][] = [
                    'type'    => 'course',
                    'post_id' => $post_id,
                    'title'   => (string) $course['title'],
                ];
                continue;
            }
            // action === 'new' or 'duplicate' → create fresh
            $new_id = $this->config_repository->create_course( [
                'title'      => (string) $course['title'],
                'theme'      => (string) $course['theme'],
                'objectives' => (string) $course['objectives'],
                'content'    => (string) $course['content'],
                'status'     => 'publish',
            ] );
            if ( is_wp_error( $new_id ) ) {
                $report['errors'][] = $new_id->get_error_message();
                continue;
            }
            $ref_to_post_id[$ref] = (int) $new_id;
            $report['created'][] = [
                'type'    => 'course',
                'post_id' => (int) $new_id,
                'title'   => (string) $course['title'],
            ];
        }
    }

    /**
     * @param array<int, array<string,mixed>>  $modules
     * @param array<string, string>            $decisions
     * @param array<string, int>               $ref_to_post_id
     * @param int                              $holder_course_id  0 until first orphan forces creation.
     * @param array<string, mixed>             $report
     */
    private function apply_modules(
        array $modules,
        array $decisions,
        array &$ref_to_post_id,
        int &$holder_course_id,
        array &$report
    ) : void {
        foreach ( $modules as $module ) {
            $ref = (string) $module['file_ref'];
            $action = ( isset( $decisions[$ref] ) ? (string) $decisions[$ref] : null );
            if ( $action === null || $action === 'skip' ) {
                if ( !empty( $module['existing_match'] ) ) {
                    $report['skipped'][] = [
                        'type'   => 'module',
                        'title'  => (string) $module['title'],
                        'reason' => 'user_skip',
                    ];
                }
                continue;
            }
            $is_overwrite = $action === 'overwrite' && !empty( $module['existing_match']['post_id'] );
            // Resolve parent course from the manifest if its course was imported in the same run.
            $parent_course_id = 0;
            $parent_ref = ( isset( $module['parent_course_ref'] ) ? $module['parent_course_ref'] : null );
            if ( is_string( $parent_ref ) && isset( $ref_to_post_id[$parent_ref] ) ) {
                $parent_course_id = (int) $ref_to_post_id[$parent_ref];
            } elseif ( !$is_overwrite ) {
                // new/duplicate without a resolved parent → fall back to a holder course.
                // overwrite without a resolved parent → preserve the existing parent (handled below).
                if ( $holder_course_id === 0 ) {
                    $holder_course_id = $this->create_holder_course( $report );
                    if ( $holder_course_id === 0 ) {
                        continue;
                        // Error already logged.
                    }
                }
                $parent_course_id = $holder_course_id;
            }
            if ( $is_overwrite ) {
                $post_id = (int) $module['existing_match']['post_id'];
                $payload = [
                    'title'   => (string) $module['title'],
                    'subject' => (string) $module['subject'],
                    'context' => (string) $module['context'],
                ];
                if ( $parent_course_id > 0 ) {
                    $payload['parent_course_id'] = $parent_course_id;
                }
                $result = $this->config_repository->update_module( $post_id, $payload );
                if ( is_wp_error( $result ) ) {
                    $report['errors'][] = $result->get_error_message();
                    continue;
                }
                $ref_to_post_id[$ref] = $post_id;
                $report['overwritten'][] = [
                    'type'    => 'module',
                    'post_id' => $post_id,
                    'title'   => (string) $module['title'],
                ];
                continue;
            }
            $new_id = $this->config_repository->create_module( [
                'title'            => (string) $module['title'],
                'subject'          => (string) $module['subject'],
                'context'          => (string) $module['context'],
                'parent_course_id' => $parent_course_id,
                'status'           => 'publish',
            ] );
            if ( is_wp_error( $new_id ) ) {
                $report['errors'][] = $new_id->get_error_message();
                continue;
            }
            $ref_to_post_id[$ref] = (int) $new_id;
            $report['created'][] = [
                'type'    => 'module',
                'post_id' => (int) $new_id,
                'title'   => (string) $module['title'],
            ];
        }
    }

    private function create_holder_course( array &$report ) : int {
        $title = sprintf( 
            /* translators: %s: current date */
            __( 'Imported (orphan parents) — %s', 'slidemind' ),
            wp_date( get_option( 'date_format' ) )
         );
        $id = $this->config_repository->create_course( [
            'title'      => $title,
            'theme'      => '',
            'objectives' => '',
            'content'    => '',
            'status'     => 'publish',
        ] );
        if ( is_wp_error( $id ) ) {
            $report['errors'][] = $id->get_error_message();
            return 0;
        }
        $report['holders']['courses'][] = [
            'post_id' => (int) $id,
            'title'   => $title,
        ];
        return (int) $id;
    }

    private function apply_slides(
        array $slides,
        array $decisions,
        array &$ref_to_post_id,
        int &$holder_course_id,
        int &$holder_module_id,
        array &$report
    ) : void {
        foreach ( $slides as $slide ) {
            $ref = (string) $slide['file_ref'];
            $action = ( isset( $decisions[$ref] ) ? (string) $decisions[$ref] : null );
            if ( $action === null || $action === 'skip' ) {
                if ( !empty( $slide['existing_match'] ) ) {
                    $report['skipped'][] = [
                        'type'   => 'slide',
                        'title'  => (string) ($slide['title'] ?? ''),
                        'reason' => 'user_skip',
                    ];
                }
                continue;
            }
            $is_overwrite = $action === 'overwrite' && !empty( $slide['existing_match']['post_id'] );
            $parent_module_id = 0;
            $parent_course_id = 0;
            $mod_ref = $slide['parent_module_ref'] ?? null;
            if ( is_string( $mod_ref ) && isset( $ref_to_post_id[$mod_ref] ) ) {
                $parent_module_id = (int) $ref_to_post_id[$mod_ref];
            }
            $course_ref = $slide['parent_course_ref'] ?? null;
            if ( is_string( $course_ref ) && isset( $ref_to_post_id[$course_ref] ) ) {
                $parent_course_id = (int) $ref_to_post_id[$course_ref];
            }
            if ( $parent_module_id === 0 && !$is_overwrite ) {
                // new/duplicate without a resolved parent → fall back to a holder module.
                // overwrite without a resolved parent → preserve the existing parent (handled below).
                if ( $holder_course_id === 0 ) {
                    $holder_course_id = $this->create_holder_course( $report );
                    if ( $holder_course_id === 0 ) {
                        continue;
                    }
                }
                if ( $holder_module_id === 0 ) {
                    $holder_module_id = $this->create_holder_module( $holder_course_id, $report );
                    if ( $holder_module_id === 0 ) {
                        continue;
                    }
                }
                $parent_module_id = $holder_module_id;
                $parent_course_id = $holder_course_id;
            }
            $payload = $this->slide_payload_for_write( $slide, $parent_module_id, $parent_course_id );
            if ( $is_overwrite && $parent_module_id === 0 ) {
                // Preserve existing parent meta when the manifest did not resolve one.
                unset($payload['parent_module_id'], $payload['parent_course_id']);
            }
            if ( $is_overwrite ) {
                $post_id = (int) $slide['existing_match']['post_id'];
                $result = $this->config_repository->update_slide( $post_id, $payload );
                if ( is_wp_error( $result ) ) {
                    $report['errors'][] = $result->get_error_message();
                    continue;
                }
                $ref_to_post_id[$ref] = $post_id;
                $report['overwritten'][] = [
                    'type'    => 'slide',
                    'post_id' => $post_id,
                    'title'   => (string) ($slide['title'] ?? ''),
                ];
                continue;
            }
            $payload['status'] = 'draft';
            $new_id = $this->config_repository->create_slide( $payload );
            if ( is_wp_error( $new_id ) ) {
                $report['errors'][] = $new_id->get_error_message();
                continue;
            }
            $ref_to_post_id[$ref] = (int) $new_id;
            $report['created'][] = [
                'type'    => 'slide',
                'post_id' => (int) $new_id,
                'title'   => (string) ($slide['title'] ?? ''),
            ];
        }
    }

    private function create_holder_module( int $course_id, array &$report ) : int {
        $title = __( 'Imported slides', 'slidemind' );
        $id = $this->config_repository->create_module( [
            'title'            => $title,
            'subject'          => '',
            'context'          => '',
            'parent_course_id' => $course_id,
            'status'           => 'publish',
        ] );
        if ( is_wp_error( $id ) ) {
            $report['errors'][] = $id->get_error_message();
            return 0;
        }
        $report['holders']['modules'][] = [
            'post_id' => (int) $id,
            'title'   => $title,
        ];
        return (int) $id;
    }

    /**
     * Project a slide DTO to the shape accepted by ConfigRepository write methods.
     * Never writes api_key.
     */
    private function slide_payload_for_write( array $slide, int $module_id, int $course_id ) : array {
        $slide_number = ( isset( $slide['slide_number'] ) && $slide['slide_number'] !== null && $slide['slide_number'] !== '' ? (int) $slide['slide_number'] : '' );
        return [
            'title'                 => (string) ($slide['title'] ?? ''),
            'parent_course_id'      => $course_id,
            'parent_module_id'      => $module_id,
            'slide_number'          => $slide_number,
            'model_id'              => (string) ($slide['model_id'] ?? ''),
            'system_prompt'         => (string) ($slide['system_prompt'] ?? ''),
            'temperature'           => ( isset( $slide['temperature'] ) ? (float) $slide['temperature'] : 0.7 ),
            'max_tokens'            => ( isset( $slide['max_tokens'] ) ? (int) $slide['max_tokens'] : 1024 ),
            'variables'             => $slide['variables'] ?? [],
            'override_course'       => $slide['override_course'] ?? [],
            'override_module'       => $slide['override_module'] ?? [],
            'output_variables'      => $slide['output_variables'] ?? [],
            'notes'                 => (string) ($slide['notes'] ?? ''),
            'widget_mode'           => (string) ($slide['widget_mode'] ?? 'qa'),
            'max_turns'             => ( isset( $slide['max_turns'] ) ? (int) $slide['max_turns'] : 0 ),
            'closing_message'       => (string) ($slide['closing_message'] ?? ''),
            'opening_message'       => (string) ($slide['opening_message'] ?? ''),
            'graceful_close'        => (string) ($slide['graceful_close'] ?? '0'),
            'graceful_close_prompt' => (string) ($slide['graceful_close_prompt'] ?? ''),
        ];
    }

    /**
     * Merge file settings into current settings. Strips API keys defensively
     * even though FullExporter already excludes them at export time.
     */
    private function apply_settings( array $file_settings ) : bool {
        $current = get_option( 'slidemind_settings', [] );
        if ( !is_array( $current ) ) {
            $current = [];
        }
        foreach ( array_keys( $file_settings ) as $key ) {
            if ( preg_match( '/api[_-]?key|secret|token/i', (string) $key ) ) {
                unset($file_settings[$key]);
            }
        }
        unset($file_settings['cors_test_mode'], $file_settings['cors_test_mode_until']);
        // Merge: file values override existing for keys present; other keys untouched.
        $merged = array_merge( $current, $file_settings );
        // `global_provider` is its own option; apply separately if provided.
        if ( isset( $merged['global_provider'] ) ) {
            update_option( 'slidemind_global_provider', sanitize_key( (string) $merged['global_provider'] ) );
            unset($merged['global_provider']);
        }
        update_option( 'slidemind_settings', $merged );
        return true;
    }

    /**
     * Merge selected memory definitions, keyed by `id`. Skip duplicates.
     *
     * @param array<int, array<string,mixed>> $definitions
     * @param array<int, string>              $selected_ids  Whitelist; empty = import all.
     * @return int Number of newly added definitions.
     */
    private function apply_memory_definitions( array $definitions, array $selected_ids ) : int {
        if ( empty( $definitions ) ) {
            return 0;
        }
        $existing = get_option( 'slidemind_memory_definitions', [] );
        if ( !is_array( $existing ) ) {
            $existing = [];
        }
        $existing_index = [];
        foreach ( $existing as $def ) {
            if ( isset( $def['id'] ) ) {
                $existing_index[(string) $def['id']] = true;
            }
        }
        $whitelist = ( !empty( $selected_ids ) ? array_flip( $selected_ids ) : null );
        $added = 0;
        foreach ( $definitions as $def ) {
            if ( !is_array( $def ) || !isset( $def['id'] ) ) {
                continue;
            }
            $id = (string) $def['id'];
            if ( $whitelist !== null && !isset( $whitelist[$id] ) ) {
                continue;
            }
            if ( isset( $existing_index[$id] ) ) {
                continue;
            }
            $existing[] = $def;
            $existing_index[$id] = true;
            $added++;
        }
        if ( $added > 0 ) {
            update_option( 'slidemind_memory_definitions', $existing );
        }
        return $added;
    }

    /**
     * Validate paid-plan gates against the requested import.
     *
     * Free is read-only on courses and modules (no UI to create them) — any
     * import that touches those types must require a paid plan, including
     * 'overwrite' actions.
     *
     * @return bool|\WP_Error Returns true on success, WP_Error when blocked.
     */
    public function validate_limits( ImportManifest $manifest, array $decisions ) : bool|\WP_Error {
        return true;
    }

    private function apply_templates( array $templates, array $decisions, array &$report ) : void {
        foreach ( $templates as $tpl ) {
            $ref = (string) $tpl['file_ref'];
            $action = ( isset( $decisions[$ref] ) ? (string) $decisions[$ref] : null );
            if ( $action === null || $action === 'skip' ) {
                if ( !empty( $tpl['existing_match'] ) ) {
                    $report['skipped'][] = [
                        'type'   => 'template',
                        'title'  => (string) ($tpl['title'] ?? ''),
                        'reason' => 'user_skip',
                    ];
                }
                continue;
            }
            $payload = [
                'title'            => (string) ($tpl['title'] ?? ''),
                'slug'             => (string) ($tpl['slug'] ?? ''),
                'description'      => (string) ($tpl['description'] ?? ''),
                'icon'             => (string) ($tpl['icon'] ?? ''),
                'system_prompt'    => (string) ($tpl['system_prompt'] ?? ''),
                'temperature'      => ( isset( $tpl['temperature'] ) ? (float) $tpl['temperature'] : 0.7 ),
                'max_tokens'       => ( isset( $tpl['max_tokens'] ) ? (int) $tpl['max_tokens'] : 1024 ),
                'variables'        => $tpl['variables'] ?? [],
                'widget_mode'      => (string) ($tpl['widget_mode'] ?? 'chat'),
                'max_turns'        => ( isset( $tpl['max_turns'] ) ? (int) $tpl['max_turns'] : 0 ),
                'closing_message'  => (string) ($tpl['closing_message'] ?? ''),
                'opening_message'  => (string) ($tpl['opening_message'] ?? ''),
                'graceful_close'   => (bool) ($tpl['graceful_close'] ?? false),
                'output_variables' => $tpl['output_variables'] ?? [],
            ];
            if ( $action === 'overwrite' && !empty( $tpl['existing_match']['slug'] ) ) {
                $result = $this->template_manager->update_by_slug( (string) $tpl['existing_match']['slug'], $payload );
                if ( is_wp_error( $result ) ) {
                    $report['errors'][] = $result->get_error_message();
                    continue;
                }
                $report['overwritten'][] = [
                    'type'  => 'template',
                    'slug'  => (string) $tpl['existing_match']['slug'],
                    'title' => $payload['title'],
                ];
                continue;
            }
            $result = $this->template_manager->create_custom( $payload );
            if ( is_wp_error( $result ) ) {
                $report['errors'][] = $result->get_error_message();
                continue;
            }
            $report['created'][] = [
                'type'  => 'template',
                'slug'  => (string) $payload['slug'],
                'title' => $payload['title'],
            ];
        }
    }

}
