<?php

namespace AIPT\Api\TaxonomyGenerator;

use AIPT\Api\Services\RestAPIClientFactory;

class TaxonomyGeneratorService {
    private TaxonomyDiscoveryService $discovery_service;

    private TaxonomyDataFormatter $data_formatter;

    private TaxonomyPromptFactory $prompt_factory;

    private RestAPIClientFactory $client_factory;

    public function __construct(
        ?TaxonomyDiscoveryService $discovery_service = null,
        ?TaxonomyDataFormatter $data_formatter = null,
        ?TaxonomyPromptFactory $prompt_factory = null,
        ?RestAPIClientFactory $client_factory = null
    ) {
        $this->discovery_service = $discovery_service ?: new TaxonomyDiscoveryService();
        $this->data_formatter    = $data_formatter ?: new TaxonomyDataFormatter( $this->discovery_service );
        $this->prompt_factory    = $prompt_factory ?: new TaxonomyPromptFactory();
        $this->client_factory    = $client_factory ?: new RestAPIClientFactory();
    }

    /**
     * @param array  $term_ids Term IDs.
     * @param string $taxonomy Taxonomy name.
     * @param array  $sources  Selected context sources.
     * @param array  $options  Runtime generation options.
     *
     * @return array{results: array, errors: array, skipped: array}
     */
    public function generate( array $term_ids, string $taxonomy, array $sources, array $options ): array {
        $taxonomy = (string) sanitize_key( $taxonomy );

        $normalized_term_ids = array_values(
            array_unique(
                array_filter(
                    array_map( 'absint', $term_ids )
                )
            )
        );

		$normalized_sources = $this->normalize_sources( $sources );

		$controlled = $this->applyPremiumControls( $normalized_sources, $options );
		$normalized_sources = $controlled['sources'];
		$options            = $controlled['options'];

		$skip_existing      = ! empty( $options['skipExisting'] );

        $provider = isset( $options['provider'] ) && is_string( $options['provider'] )
            ? sanitize_key( $options['provider'] )
            : sanitize_key( (string) get_option( 'aipt_api_provider', 'openai' ) );
        $provider = (string) $provider;

        $selected_model = isset( $options['model'] ) && is_string( $options['model'] )
            ? sanitize_text_field( $options['model'] )
            : '';

        if ( '' === $selected_model ) {
            $selected_model = $this->client_factory->getDefaultModel( $provider );
        }

        $model = $this->client_factory->normalizeModel( $provider, $selected_model );

        $results = [];
        $errors  = [];
        $skipped = [];

        foreach ( $normalized_term_ids as $term_id ) {
            try {
                $term = $this->discovery_service->getTermById( $term_id, $taxonomy );

                if ( ! is_array( $term ) ) {
                    throw new \RuntimeException( __( 'Term could not be loaded.', 'ai-product-tools' ) );
                }

                if ( $skip_existing && ! empty( $term['description'] ) ) {
                    $skipped[] = [
                        'term_id'   => (int) $term_id,
                        'term_name' => (string) ( $term['name'] ?? '' ),
                        'taxonomy'  => $taxonomy,
                        'reason'    => __( 'Term already has a description.', 'ai-product-tools' ),
                    ];
                    continue;
                }

                $term_context = $this->data_formatter->formatTermContext( $term_id, $taxonomy, $normalized_sources, $options );
                $taxonomy_obj = call_user_func( 'get_taxonomy', $taxonomy );

                if ( $taxonomy_obj && ! empty( $taxonomy_obj->label ) ) {
                    $term_context['taxonomy_label'] = (string) $taxonomy_obj->label;
                }

				$filtered_options = apply_filters( 'aipt_taxonomy_generator_options', $options, $term_context );
				if ( ! is_array( $filtered_options ) ) {
					$filtered_options = $options;
				}

				$prompt_pair = $this->prompt_factory->buildPrompt( $term_context, $filtered_options );
                $client      = $this->client_factory->create( $provider );

                $generated_content = $client->generate_description(
                    (string) $prompt_pair['system'],
                    (string) $prompt_pair['user'],
                    $model,
                    null
                );

                $sanitized_content = wp_kses_post( $generated_content );

                $results[] = [
                    'term_id'             => (int) $term_id,
                    'term_name'           => (string) ( $term['name'] ?? '' ),
                    'taxonomy'            => $taxonomy,
                    'content'             => $sanitized_content,
                    'status'              => 'pending',
                    'originalContent'     => $sanitized_content,
                    'existingDescription' => (string) ( $term['description'] ?? '' ),
                ];

            } catch ( \Throwable $throwable ) {
                $errors[] = [
                    'term_id'  => (int) $term_id,
                    'taxonomy' => $taxonomy,
                    'message'  => sanitize_text_field( $throwable->getMessage() ),
                ];
            }
        }

        return [
            'results' => $results,
            'errors'  => $errors,
            'skipped' => $skipped,
        ];
    }

    /**
     * @param array $applications Requested apply payload.
     *
     * @return array<int, array<string, mixed>>
     */
    public function applyDescriptions( array $applications ): array {
        $results = [];

        foreach ( $applications as $application ) {
            $term_id   = isset( $application['term_id'] ) ? absint( $application['term_id'] ) : 0;
            $taxonomy  = isset( $application['taxonomy'] ) ? sanitize_key( (string) $application['taxonomy'] ) : '';
            $content   = isset( $application['content'] ) ? wp_kses_post( (string) $application['content'] ) : '';
            $write_mode = isset( $application['writeMode'] ) ? sanitize_key( (string) $application['writeMode'] ) : 'overwrite';

            if ( ! in_array( $write_mode, [ 'overwrite', 'append' ], true ) ) {
                $write_mode = 'overwrite';
            }

            if ( $term_id <= 0 || '' === $taxonomy ) {
                $results[] = [
                    'term_id'  => $term_id,
                    'taxonomy' => $taxonomy,
                    'success'  => false,
                    'message'  => __( 'Invalid term application payload.', 'ai-product-tools' ),
                ];
                continue;
            }

            $term = $this->discovery_service->getTermById( $term_id, $taxonomy );

            if ( ! is_array( $term ) ) {
                $results[] = [
                    'term_id'  => $term_id,
                    'taxonomy' => $taxonomy,
                    'success'  => false,
                    'message'  => __( 'Term could not be loaded.', 'ai-product-tools' ),
                ];
                continue;
            }

            $final_content = $content;

            if ( 'append' === $write_mode ) {
                $existing_content = isset( $term['description'] ) ? (string) $term['description'] : '';
                $final_content    = '' !== trim( $existing_content )
                    ? $existing_content . "\n\n" . $content
                    : $content;
            }

            $update_result = call_user_func(
                'wp_update_term',
                $term_id,
                $taxonomy,
                [
                    'description' => $final_content,
                ]
            );

            if ( is_wp_error( $update_result ) ) {
                $results[] = [
                    'term_id'  => $term_id,
                    'taxonomy' => $taxonomy,
                    'success'  => false,
                    'message'  => sanitize_text_field( $update_result->get_error_message() ),
                ];
                continue;
            }

            $results[] = [
                'term_id'  => $term_id,
                'taxonomy' => $taxonomy,
                'success'  => true,
                'message'  => __( 'Description applied successfully.', 'ai-product-tools' ),
                'mode'     => $write_mode,
            ];
        }

        return $results;
    }

    /**
     * @param array $sources Raw source values.
     *
     * @return array
     */
    private function normalize_sources( array $sources ): array {
        $normalized = [];

        foreach ( $sources as $source ) {
            if ( ! is_string( $source ) ) {
                continue;
            }

            $key = sanitize_key( $source );

            if ( '' !== $key ) {
                $normalized[] = $key;
            }
        }

        if ( ! in_array( 'term_name', $normalized, true ) ) {
            $normalized[] = 'term_name';
        }

        return array_values( array_unique( $normalized ) );
    }

	/**
	 * Free-tier allowed data sources.
	 *
	 * @var string[]
	 */
	private const FREE_SOURCES = [
		'term_name',
		'parent_context',
		'term_description',
		'product_categories',
		'product_names',
		'product_prices',
		'product_tags',
	];

	/**
	 * Free-tier allowed generation purposes.
	 *
	 * @var string[]
	 */
	private const FREE_PURPOSES = [
		'seo_description',
		'marketing_overview',
		'technical_summary',
		'brand_story',
	];

	/**
	 * Free-tier maximum product samples.
	 *
	 * @var int
	 */
	private const FREE_MAX_SAMPLES = 10;

	/**
	 * Apply premium feature controls to generation parameters.
	 *
	 * Enforces free-tier defaults for sources, purpose, and sample limit.
	 * Premium module hooks into the apply_filters calls to restore premium values.
	 *
	 * @param array  $sources Raw sources from request.
	 * @param array  $options Raw options from request.
	 * @return array{sources: array, options: array} Controlled parameters.
	 */
	private function applyPremiumControls( array $sources, array $options ): array {
		/**
		 * Filter allowed taxonomy sources.
		 *
		 * Premium module hooks here to add premium sources
		 * (product_descriptions, product_attributes).
		 *
		 * @since 2.5.0
		 * @param string[] $allowed_sources Free-tier allowed sources.
		 */
		$allowed_sources = apply_filters( 'aipt_taxonomy_allowed_sources', self::FREE_SOURCES );

		if ( ! is_array( $allowed_sources ) ) {
			$allowed_sources = self::FREE_SOURCES;
		}

		$controlled_sources = array_values(
			array_intersect( $sources, $allowed_sources )
		);

		if ( ! in_array( 'term_name', $controlled_sources, true ) ) {
			$controlled_sources[] = 'term_name';
		}

		/**
		 * Filter allowed taxonomy purposes.
		 *
		 * Premium module hooks here to add custom purpose.
		 *
		 * @since 2.5.0
		 * @param string[] $allowed_purposes Free-tier allowed purposes.
		 */
		$allowed_purposes = apply_filters( 'aipt_taxonomy_allowed_purposes', self::FREE_PURPOSES );

		if ( ! is_array( $allowed_purposes ) ) {
			$allowed_purposes = self::FREE_PURPOSES;
		}

		$controlled_options = $options;

		if ( isset( $controlled_options['purpose'] ) ) {
			$purpose = sanitize_key( (string) $controlled_options['purpose'] );
			if ( ! in_array( $purpose, $allowed_purposes, true ) ) {
				$controlled_options['purpose'] = self::FREE_PURPOSES[0];
			}
		}

		/**
		 * Filter maximum product samples for taxonomy generation.
		 *
		 * Premium module hooks here to increase the sample limit.
		 *
		 * @since 2.5.0
		 * @param int $max_samples Free-tier maximum samples.
		 */
		$max_samples = apply_filters( 'aipt_taxonomy_max_samples', self::FREE_MAX_SAMPLES );

		if ( ! is_int( $max_samples ) || $max_samples < 1 ) {
			$max_samples = self::FREE_MAX_SAMPLES;
		}

		if ( isset( $controlled_options['maxProductSamples'] ) ) {
			$requested = absint( $controlled_options['maxProductSamples'] );
			$controlled_options['maxProductSamples'] = min( $requested, $max_samples );
		}

		/**
		 * Filter whether the additional prompt feature is allowed.
		 *
		 * Premium module hooks here to enable the additional prompt.
		 *
		 * @since 2.5.0
		 * @param bool $allowed Whether additional prompt is allowed. Default false (free tier).
		 */
		$additional_prompt_allowed = apply_filters( 'aipt_taxonomy_additional_prompt_allowed', false );

		if ( ! $additional_prompt_allowed && isset( $controlled_options['additionalPrompt'] ) ) {
			$controlled_options['additionalPrompt'] = '';
		}

		return [
			'sources' => $controlled_sources,
			'options' => $controlled_options,
		];
	}
}
