<?php
/**
 * WP Blog Agent Claude API Integration
 *
 * @package WP_Blog_Agent
 * @since 1.0.0
 */

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * WBAG_Api class for handling Anthropic Claude API communication
 *
 * @since 1.0.0
 */
class WBAG_Api {

	/**
	 * Anthropic Claude API endpoint
	 *
	 * @var string
	 */
	const API_ENDPOINT = 'https://api.anthropic.com/v1/messages';

	/**
	 * Anthropic API version header
	 *
	 * @var string
	 */
	const API_VERSION = '2023-06-01';

	/**
	 * Get singleton instance
	 *
	 * @return WBAG_Api
	 * @since 1.0.0
	 */
	public static function get_instance() {
		static $instance = null;
		if ( null === $instance ) {
			$instance = new self();
		}
		return $instance;
	}

	/**
	 * Test API connection
	 *
	 * Sends a simple test message to verify the API key is working.
	 *
	 * @return array {
	 *     Response array.
	 *
	 *     @type bool   $success Whether the connection was successful.
	 *     @type string $message Response message or error details.
	 * }
	 * @since 1.0.0
	 */
	public static function test_connection() {
		$settings = get_option( 'wbag_settings', array() );

		if ( empty( $settings['api_key'] ) ) {
			return array(
				'success' => false,
				'message' => __( 'API key is not configured.', 'wp-blog-agent' ),
			);
		}

		$result = self::send_message(
			__( 'You are a helpful assistant.', 'wp-blog-agent' ),
			__( 'Hello', 'wp-blog-agent' ),
			100
		);

		if ( is_wp_error( $result ) ) {
			return array(
				'success' => false,
				'message' => $result->get_error_message(),
			);
		}

		return array(
			'success' => true,
			'message' => __( 'API connection successful!', 'wp-blog-agent' ),
		);
	}

	/**
	 * Send message to Claude API
	 *
	 * @param string $system_prompt System prompt for Claude.
	 * @param string $user_message  User message to send.
	 * @param int    $max_tokens    Maximum tokens in response. Default 8192.
	 *
	 * @return string|WP_Error Response text content or WP_Error on failure.
	 * @since 1.0.0
	 */
	public static function send_message( $system_prompt, $user_message, $max_tokens = 8192 ) {
		$settings = get_option( 'wbag_settings', array() );

		// Validate API key
		if ( empty( $settings['api_key'] ) ) {
			return new WP_Error(
				'wbag_missing_api_key',
				__( 'Claude API key is not configured. Please add it in the plugin settings.', 'wp-blog-agent' )
			);
		}

		// Get API model with validation — old model IDs cause "credit balance too low" errors
		$valid_models = array( 'claude-sonnet-4-6', 'claude-opus-4-6', 'claude-haiku-4-5-20251001' );
		$api_model    = ( ! empty( $settings['api_model'] ) && in_array( $settings['api_model'], $valid_models, true ) )
			? $settings['api_model']
			: 'claude-sonnet-4-6';

		// Auto-fix outdated model ID in database so user doesn't have to re-save
		if ( ! empty( $settings['api_model'] ) && $settings['api_model'] !== $api_model ) {
			$settings['api_model'] = $api_model;
			update_option( 'wbag_settings', $settings );
		}

		// Prepare request body
		$body = array(
			'model'       => $api_model,
			'max_tokens'  => intval( $max_tokens ),
			'system'      => $system_prompt,
			'messages'    => array(
				array(
					'role'    => 'user',
					'content' => $user_message,
				),
			),
		);

		// Prepare request headers
		$headers = array(
			'x-api-key'          => $settings['api_key'],
			'anthropic-version'  => self::API_VERSION,
			'content-type'       => 'application/json',
		);

		// Make API request
		$response = wp_remote_post(
			self::API_ENDPOINT,
			array(
				'headers'   => $headers,
				'body'      => wp_json_encode( $body ),
				'timeout'   => 120,
				'sslverify' => true,
			)
		);

		// Handle HTTP errors
		if ( is_wp_error( $response ) ) {
			return new WP_Error(
				'wbag_api_http_error',
				sprintf(
					/* translators: %s: HTTP error message from the API request */
					__( 'API request failed: %s', 'wp-blog-agent' ),
					$response->get_error_message()
				)
			);
		}

		// Get response body
		$response_body = wp_remote_retrieve_body( $response );
		$response_code = wp_remote_retrieve_response_code( $response );

		// Try to decode JSON response
		$decoded = json_decode( $response_body, true );

		// Handle JSON decode errors
		if ( null === $decoded ) {
			return new WP_Error(
				'wbag_api_json_error',
				__( 'Failed to parse API response.', 'wp-blog-agent' )
			);
		}

		// Handle API error responses
		if ( $response_code >= 400 ) {
			$error_message = isset( $decoded['error']['message'] ) ? $decoded['error']['message'] : __( 'Unknown API error', 'wp-blog-agent' );
			return new WP_Error(
				'wbag_api_error',
				sprintf(
					/* translators: %1$d: HTTP response code, %2$s: error message from Claude API */
					__( 'Claude API Error (%1$d): %2$s', 'wp-blog-agent' ),
					$response_code,
					$error_message
				)
			);
		}

		// Extract text content from response
		return self::extract_response_text( $decoded );
	}

	/**
	 * Generate blog post topics
	 *
	 * @param string $context Context about existing posts and niche.
	 *
	 * @return array|WP_Error Array of topic objects or WP_Error on failure.
	 * @since 1.0.0
	 */
	public static function generate_topics( $context ) {
		$settings = get_option( 'wbag_settings', array() );

		// Build system prompt for topic generation
		/* translators: %1$s: blog niche, %2$s: target audience, %3$s: subtopics */
		$system_prompt = sprintf(
			__( 'You are an expert blog content strategist known for creative, diverse topic ideas. Your task is to generate blog post topics based on the user\'s niche and existing content.

Niche: %1$s
Target Audience: %2$s
Subtopics: %3$s

CRITICAL — TOPIC DIVERSITY:
Each generated topic MUST use a different content format/angle. Mix from these types:
- How-to / Step-by-step guide
- Comparison (A vs B)
- Listicle ("7 ways to...", "Top 10...")
- Myth-busting / Common mistakes
- Case study / Real-world example
- Beginner\'s guide / Explained simply
- Expert deep-dive / Advanced techniques
- Opinion piece / Hot take
- Trend analysis / Future outlook
- Problem-solution ("Why X fails and how to fix it")
- Checklist / Template
- Interview-style / Q&A

CRITICAL — TITLE VARIETY:
- NEVER start multiple titles with the same word or structure.
- Vary sentence patterns: questions, statements, "How to...", numbers, challenges.
- Use vivid, specific language instead of generic phrases.
- Include concrete details (years, numbers, specific tools) where appropriate.
- Avoid generic clickbait patterns like "The Ultimate Guide" or "Everything You Need to Know".

For each topic, provide:
- title: The blog post title (creative, specific, attention-grabbing)
- slug: URL-friendly slug
- primary_keyword: Main keyword to target
- search_intent: User intent (informational, commercial, transactional)
- content_type: The format/angle used (e.g. "how-to", "comparison", "myth-busting", "listicle", "deep-dive", "trend-analysis", etc.)
- content_gap: What competing blogs are missing
- subtopic: Related subtopic to cover
- difficulty: SEO difficulty (1-100)
- priority_score: Priority ranking (1-10)
- description: Brief description (2-3 sentences explaining the unique angle)
- competitor_inspired: If the topic was inspired by a competitor blog post, set this to the competitor domain (e.g. "example.de"). If the topic is an original idea not based on any competitor, set this to "" (empty string).

Return ONLY a valid JSON array of topic objects.', 'wp-blog-agent' ),
			! empty( $settings['niche'] ) ? sanitize_text_field( $settings['niche'] ) : __( 'General', 'wp-blog-agent' ),
			! empty( $settings['target_audience'] ) ? sanitize_text_field( $settings['target_audience'] ) : __( 'General audience', 'wp-blog-agent' ),
			! empty( $settings['subtopics'] ) ? sanitize_text_field( $settings['subtopics'] ) : __( 'Not specified', 'wp-blog-agent' )
		);

		$result = self::send_message( $system_prompt, $context, 4096 );

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		// Extract and parse JSON from response
		$topics = self::extract_json_from_response( $result );

		if ( is_wp_error( $topics ) ) {
			return $topics;
		}

		// Validate topics structure
		if ( ! is_array( $topics ) ) {
			return new WP_Error(
				'wbag_invalid_topics_format',
				__( 'Topics response is not a valid array.', 'wp-blog-agent' )
			);
		}

		return $topics;
	}

	/**
	 * Generate blog post content
	 *
	 * @param array $topic    Topic data with title, keywords, etc.
	 * @param array $settings Plugin settings.
	 *
	 * @return string|WP_Error Generated HTML content or WP_Error on failure.
	 * @since 1.0.0
	 */
	public static function generate_blog_post( $topic, $settings ) {
		// Validate inputs
		if ( ! isset( $topic['title'] ) || ! isset( $topic['primary_keyword'] ) ) {
			return new WP_Error(
				'wbag_invalid_topic',
				__( 'Topic must include title and primary_keyword.', 'wp-blog-agent' )
			);
		}

		// Prepare CTA section
		$cta_section = '';
		if ( ! empty( $settings['cta_text'] ) && ! empty( $settings['cta_url'] ) ) {
			$cta_color = ! empty( $settings['cta_color'] ) ? sanitize_hex_color( $settings['cta_color'] ) : '#FF6B35';
			/* translators: %1$s: CTA button text, %2$s: CTA target URL, %3$s: CTA button color */
			$cta_section = sprintf(
				__( '

Call-to-Action Section:
- Text: %1$s
- URL: %2$s
- Color: %3$s', 'wp-blog-agent' ),
				sanitize_text_field( $settings['cta_text'] ),
				esc_url( $settings['cta_url'] ),
				$cta_color
			);
		}

		// Prepare FAQ section
		$faq_count = ! empty( $settings['faq_count'] ) ? intval( $settings['faq_count'] ) : 10;

		// Prepare word count range
		$word_count_min = ! empty( $settings['word_count_min'] ) ? intval( $settings['word_count_min'] ) : 2500;
		$word_count_max = ! empty( $settings['word_count_max'] ) ? intval( $settings['word_count_max'] ) : 4000;

		// Build comprehensive system prompt
		/* translators: %1$d: FAQ count, %2$s: CTA section, %3$s: author name, %4$s: language, %5$s: formal address, %6$s: focus keyword, %7$s: primary keyword, %8$d: internal links count, %9$d: external links count, %10$d: min word count, %11$d: max word count, %12$s: topic title, %13$s: description, %14$s: search intent, %15$s: content gap */
		$system_prompt = sprintf(
			__( 'You are a professional blog writer. Write a high-quality, SEO-optimized blog post.

BLOG POST STRUCTURE:
- H1 heading with focus keyword
- Hook paragraph (engaging introduction)
- H2 sections with H3 subsections as needed
- Proper internal and external links
- FAQ section with %1$d frequently asked questions
- Related posts section with 3 related topics
- CTA (Call-to-Action) section%2$s

AUTHOR IDENTITY:
- Author: %3$s
- Language: %4$s
- Formal Address: %5$s — Use this form of address CONSISTENTLY throughout the ENTIRE post. NEVER switch between Sie and Du. Every single paragraph, FAQ answer, CTA, and heading must use the same form of address.

SEO REQUIREMENTS:
- Focus Keyword: %6$s
- Primary Keyword: %7$s
- Keyword Density: 0.5-1.5%%
- Internal Links: %8$d
- External Links: %9$d
- Meta Focus: Single keyword strategy

CONTENT SPECIFICATIONS:
- Word Count Range: %10$d - %11$d words
- Content Quality: Comprehensive, original, engaging
- Format: HTML with proper semantic tags (h1-h3, p, ul, ol)
- Include introduction, body sections, conclusion
- Ensure natural keyword placement

Topic: %12$s
Description: %13$s
Search Intent: %14$s
Content Gap: %15$s

Generate the complete blog post in HTML format. Use semantic HTML tags. Make it engaging, informative, and well-structured.', 'wp-blog-agent' ),
			$faq_count,
			$cta_section,
			! empty( $settings['author_name'] ) ? sanitize_text_field( $settings['author_name'] ) : ( ! empty( $settings['post_author_id'] ) ? ( ( $u = get_userdata( absint( $settings['post_author_id'] ) ) ) ? $u->display_name : __( 'Admin', 'wp-blog-agent' ) ) : __( 'Admin', 'wp-blog-agent' ) ),
			! empty( $settings['language'] ) ? sanitize_text_field( $settings['language'] ) : 'de',
			! empty( $settings['formal_address'] ) ? sanitize_text_field( $settings['formal_address'] ) : 'Sie',
			sanitize_text_field( $topic['primary_keyword'] ),
			sanitize_text_field( $topic['primary_keyword'] ),
			! empty( $settings['internal_links'] ) ? intval( $settings['internal_links'] ) : 3,
			! empty( $settings['external_links'] ) ? intval( $settings['external_links'] ) : 2,
			$word_count_min,
			$word_count_max,
			sanitize_text_field( $topic['title'] ),
			! empty( $topic['description'] ) ? sanitize_text_field( $topic['description'] ) : '',
			! empty( $topic['search_intent'] ) ? sanitize_text_field( $topic['search_intent'] ) : '',
			! empty( $topic['content_gap'] ) ? sanitize_text_field( $topic['content_gap'] ) : ''
		);

		/* translators: %s: blog post topic title */
		$user_message = sprintf(
			__( 'Please write a comprehensive blog post about: %s', 'wp-blog-agent' ),
			sanitize_text_field( $topic['title'] )
		);

		$result = self::send_message( $system_prompt, $user_message, 8192 );

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		return $result;
	}

	/**
	 * Generate image prompts for blog post
	 *
	 * @param string $topic_title   Topic title.
	 * @param string $topic_keyword Primary keyword.
	 * @param array  $settings      Plugin settings.
	 *
	 * @return array|WP_Error Array of image prompts or WP_Error on failure.
	 * @since 1.0.0
	 */
	public static function generate_image_prompts( $topic_title, $topic_keyword, $settings ) {
		$image_count = ! empty( $settings['image_count'] ) ? intval( $settings['image_count'] ) : 2;
		$image_size = ! empty( $settings['image_size'] ) ? sanitize_text_field( $settings['image_size'] ) : '1024x572';
		$image_style = ! empty( $settings['image_style'] ) ? sanitize_text_field( $settings['image_style'] ) : 'professional';
		$prompt_lang = ! empty( $settings['image_prompt_lang'] ) ? sanitize_text_field( $settings['image_prompt_lang'] ) : 'en';

		/* translators: %1$s: image style, %2$s: image dimensions, %3$s: prompt language, %4$d: number of images to generate */
		$system_prompt = sprintf(
			__( 'You are an expert AI image prompt generator. Generate creative and detailed prompts for AI image generation tools.

Style: %1$s
Image Size: %2$s
Language: %3$s

For each image, provide a JSON object with:
- purpose: What the image is for (e.g., "featured image", "section header")
- prompt_text: Detailed prompt for image generation
- alt_text: SEO-friendly alt text
- dimensions: Image dimensions

Generate %4$d image prompts for a blog post.', 'wp-blog-agent' ),
			$image_style,
			$image_size,
			$prompt_lang,
			$image_count
		);

		/* translators: %1$d: number of images, %2$s: blog post title, %3$s: primary keyword */
		$user_message = sprintf(
			__( 'Generate %1$d image prompts for a blog post about "%2$s" with keyword "%3$s".', 'wp-blog-agent' ),
			$image_count,
			sanitize_text_field( $topic_title ),
			sanitize_text_field( $topic_keyword )
		);

		$result = self::send_message( $system_prompt, $user_message, 2048 );

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		// Extract and parse JSON from response
		$prompts = self::extract_json_from_response( $result );

		if ( is_wp_error( $prompts ) ) {
			return $prompts;
		}

		// Validate structure
		if ( ! is_array( $prompts ) ) {
			return new WP_Error(
				'wbag_invalid_prompts_format',
				__( 'Image prompts response is not a valid array.', 'wp-blog-agent' )
			);
		}

		return $prompts;
	}

	/**
	 * Generate SEO metadata
	 *
	 * @param string $title             Blog post title.
	 * @param string $content           Blog post content.
	 * @param string $primary_keyword   Primary keyword to target.
	 * @param array  $settings          Plugin settings.
	 *
	 * @return array|WP_Error Array with meta_title, meta_description, and keywords.
	 * @since 1.0.0
	 */
	public static function generate_seo_meta( $title, $content, $primary_keyword, $settings ) {
		/* translators: %1$s: blog niche, %2$s: content language */
		$system_prompt = sprintf(
			__( 'You are an SEO expert. Generate SEO metadata for a blog post.

Requirements:
- Meta Title: 50-60 characters, include primary keyword
- Meta Description: 150-160 characters, compelling, include primary keyword
- Focus Keyword: The main keyword to target
- Secondary Keywords: 3-5 related keywords

Return ONLY valid JSON with: meta_title, meta_description, focus_keyword, secondary_keywords (array)

Blog Niche: %1$s
Language: %2$s', 'wp-blog-agent' ),
			! empty( $settings['niche'] ) ? sanitize_text_field( $settings['niche'] ) : __( 'General', 'wp-blog-agent' ),
			! empty( $settings['language'] ) ? sanitize_text_field( $settings['language'] ) : 'de'
		);

		/* translators: %1$s: blog post title, %2$s: primary keyword, %3$s: content excerpt */
		$user_message = sprintf(
			__( 'Title: %1$s
Primary Keyword: %2$s

Content excerpt:
%3$s', 'wp-blog-agent' ),
			sanitize_text_field( $title ),
			sanitize_text_field( $primary_keyword ),
			wp_strip_all_tags( substr( $content, 0, 500 ) )
		);

		$result = self::send_message( $system_prompt, $user_message, 1024 );

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		// Extract and parse JSON from response
		$metadata = self::extract_json_from_response( $result );

		if ( is_wp_error( $metadata ) ) {
			return $metadata;
		}

		// Validate structure
		if ( ! is_array( $metadata ) || ! isset( $metadata['meta_title'] ) || ! isset( $metadata['meta_description'] ) ) {
			return new WP_Error(
				'wbag_invalid_metadata_format',
				__( 'SEO metadata response is not in the expected format.', 'wp-blog-agent' )
			);
		}

		return $metadata;
	}

	/**
	 * Extract text content from Claude API response
	 *
	 * @param array $response Decoded API response.
	 *
	 * @return string|WP_Error Response text or WP_Error on failure.
	 * @since 1.0.0
	 */
	private static function extract_response_text( $response ) {
		// Check if content array exists
		if ( ! isset( $response['content'] ) || ! is_array( $response['content'] ) ) {
			return new WP_Error(
				'wbag_invalid_response_format',
				__( 'Invalid response format from API.', 'wp-blog-agent' )
			);
		}

		// Get first content block (usually text)
		if ( empty( $response['content'] ) ) {
			return new WP_Error(
				'wbag_empty_response',
				__( 'Received empty response from API.', 'wp-blog-agent' )
			);
		}

		$content = $response['content'][0];

		// Extract text from content block
		if ( isset( $content['type'] ) && 'text' === $content['type'] && isset( $content['text'] ) ) {
			return $content['text'];
		}

		return new WP_Error(
			'wbag_no_text_content',
			__( 'No text content in API response.', 'wp-blog-agent' )
		);
	}

	/**
	 * Extract JSON from Claude API response
	 *
	 * Handles both raw JSON and JSON wrapped in markdown code blocks.
	 *
	 * @param string $response API response text.
	 *
	 * @return array|WP_Error Decoded JSON array or WP_Error on failure.
	 * @since 1.0.0
	 */
	private static function extract_json_from_response( $response ) {
		$response = trim( $response );

		// Remove markdown code blocks if present
		if ( strpos( $response, '```json' ) !== false ) {
			$response = preg_replace( '/```json\s*/', '', $response );
			$response = preg_replace( '/```\s*$/', '', $response );
			$response = trim( $response );
		} elseif ( strpos( $response, '```' ) !== false ) {
			$response = preg_replace( '/```\s*/', '', $response );
			$response = trim( $response );
		}

		// Try to decode JSON
		$decoded = json_decode( $response, true );

		if ( null === $decoded ) {
			return new WP_Error(
				'wbag_json_parse_error',
				__( 'Failed to parse JSON from API response.', 'wp-blog-agent' )
			);
		}

		return $decoded;
	}
}
