# Botify Code Generation Utilities
# Externalized from server.py to reduce token count while preserving functionality

import json
import urllib.parse
from pathlib import Path
from typing import Dict, Any, Optional
from fasthtml.common import A

class BotifyCodeGenerators:
    """🚀 REUSABLE BOTIFY CODE GENERATION UTILITIES"""
    
    def __init__(self, ui_constants=None):
        """Initialize with optional UI constants for consistent formatting"""
        self.ui_constants = ui_constants or self._get_default_ui_constants()
    
    def _get_default_ui_constants(self):
        """Default UI constants if none provided"""
        return {
            'CODE_FORMATTING': {
                'COMMENT_DIVIDER': '#' + '=' * 100
            }
        }
    
    def get_ui_constants(self):
        """Get UI constants for formatting"""
        return self.ui_constants

    def generate_botify_code_header(self, display_name: str, step_name: str, username: str, project_name: str, 
                                  template_info: dict = None, qualifier_config: dict = None) -> list:
        """Generate standardized header for Botify Python debugging code."""
        ui_constants = self.get_ui_constants()
        comment_divider = ui_constants['CODE_FORMATTING']['COMMENT_DIVIDER']
        
        header_lines = [
            comment_divider,
            f"# Botify API Call Example",
            f"# Generated by: {display_name}",
            f"# Step: {step_name}",
            f"# Organization: {username}",
            f"# Project: {project_name}"
        ]
        
        # Add template information if provided
        if template_info:
            header_lines.append("#")
            header_lines.append(f"# Query Template: {template_info.get('name', 'Unknown')}")
            header_lines.append(f"# Description: {template_info.get('description', 'No description available')}")
            header_lines.append(f"# Export Type: {template_info.get('export_type', 'Unknown')}")
            
            # Add qualifier information if present
            if qualifier_config and qualifier_config.get('enabled', False):
                header_lines.append("#")
                header_lines.append("# 🎯 SMART QUALIFIER SYSTEM:")
                header_lines.append(f"# This template uses automatic parameter optimization to stay under API limits.")
                param_name = qualifier_config.get('iterative_parameter_name', 'parameter')
                max_threshold = qualifier_config.get('max_value_threshold', 1000000)
                header_lines.append(f"# The system automatically finds the optimal {param_name} for ~{max_threshold:,} results.")
                if 'user_message_found' in qualifier_config:
                    msg_template = qualifier_config['user_message_found']
                    if '{param_value}' in msg_template and '{metric_value}' in msg_template:
                        header_lines.append(f"# Example: 'Optimal {param_name}: 2 (for 235,623 results)'")
        
        header_lines.extend([
            "#",
            "# 🧪 For live JupyterLab environment to experiment with queries:",
            "# http://localhost:8888/lab/tree/helpers/botify/botify_api.ipynb",
            "#",
            "# 📋 For copy/paste-able examples to use in JupyterLab:",
            "# http://localhost:5001/documentation",
            comment_divider
        ])
        
        return header_lines

    def generate_botify_token_loader(self):
        """Generate the standard Botify token loading function."""
        return '''def load_api_token() -> str:
    """Load the Botify API token from the token file."""
    try:
        if not os.path.exists(TOKEN_FILE):
            raise ValueError(f"Token file '{TOKEN_FILE}' not found.")
        with open(TOKEN_FILE) as f:
            content = f.read().strip()
            api_key = content.split('\n')[0].strip()
            if not api_key:
                raise ValueError(f"Token file '{TOKEN_FILE}' is empty.")
            return api_key
    except Exception as e:
        raise ValueError(f"Error loading API token: {str(e)}")'''

    def generate_botify_http_client(self, client_name: str, description: str):
        """Generate the standard HTTP client function for Botify APIs."""
        return f'''async def {client_name}(
    url: str,
    headers: Dict[str, str],
    payload: Dict[str, Any],
    timeout: float = 60.0
) -> Dict[str, Any]:
    """
    {description}
    """
    async with httpx.AsyncClient() as client:
        try:
            response = await client.post(
                url=url,
                headers=headers,
                json=payload,
                timeout=timeout
            )

            print(f"Status Code: {{response.status_code}}")
            response.raise_for_status()

            result = response.json()
            print(f"\nResults returned: {{len(result.get('results', []))}}")
            print(f"Total count: {{result.get('count', 'N/A')}}")

            # Show first few results for inspection
            results = result.get('results', [])
            if results:
                print("\nFirst result structure:")
                print(json.dumps(results[0], indent=2))

            return result

        except httpx.HTTPStatusError as e:
            error_msg = f"HTTP error {{e.response.status_code}}: {{e.response.text}}"
            print(f"\n❌ Error: {{error_msg}}")
            raise ValueError(error_msg)
        except Exception as e:
            error_msg = f"Unexpected error: {{str(e)}}"
            print(f"\n❌ Error: {{error_msg}}")
            raise ValueError(error_msg)'''

    def generate_botify_main_executor(self, client_function_name: str, api_description: str):
        """Generate the main execution function for Botify APIs."""
        return f'''async def main():
    """Main execution function for {api_description}"""
    try:
        result = await {client_function_name}(
            url=URL,
            headers=get_headers(),
            payload=PAYLOAD
        )
        return result

    except Exception as e:
        print(f"\n❌ {api_description} failed: {{str(e)}}")
        raise

# Execute in Jupyter Notebook:
await main()

# For standalone script execution:
# if __name__ == "__main__":
#     import asyncio
#     asyncio.run(main())'''

    def generate_botify_bqlv2_python_code(self, query_payload: dict, username: str, project_name: str, 
                                        page_size: int, jobs_payload: dict, display_name: str, 
                                        get_step_name_from_payload_func, get_configured_template_func=None, 
                                        query_templates: dict = None) -> tuple:
        """Generate Python code for BQLv2 queries (crawl, GSC)."""
        
        # Get step name from payload
        step_name = get_step_name_from_payload_func(jobs_payload)
        
        # Get template info if available
        template_info = {}
        if query_templates and get_configured_template_func:
            try:
                # Try to determine template from query structure
                collections = query_payload.get('collections', [])
                if 'search_console' in collections:
                    template_key = get_configured_template_func('gsc')
                else:
                    template_key = get_configured_template_func('analysis')
                
                if template_key and template_key in query_templates:
                    template_info = query_templates[template_key]
            except:
                pass
        
        # Generate the header
        header_lines = self.generate_botify_code_header(
            display_name=display_name,
            step_name=step_name,
            username=username,
            project_name=project_name,
            template_info=template_info
        )
        
        # Build the query URL
        query_url = f"https://api.botify.com/v1/projects/{username}/{project_name}/query?size={page_size}"
        
        # Generate the Python code
        python_code_lines = header_lines + [
            "",
            "import os",
            "import json",
            "import httpx",
            "from typing import Dict, Any",
            "",
            "# Configuration",
            "TOKEN_FILE = 'botify_token.txt'",
            "",
            self.generate_botify_token_loader(),
            "",
            "# API Configuration",
            "API_TOKEN = load_api_token()",
            f"URL = '{query_url}'",
            f"PAYLOAD = {json.dumps(query_payload, indent=4)}",
            "",
            "def get_headers() -> Dict[str, str]:",
            "    return {",
            "        'Authorization': f'Token {API_TOKEN}',",
            "        'Content-Type': 'application/json'",
            "    }",
            "",
            self.generate_botify_http_client("make_bqlv2_query", "Execute BQLv2 query for crawl or GSC data"),
            "",
            self.generate_botify_main_executor("make_bqlv2_query", "BQLv2 Query")
        ]
        
        python_code = '\n'.join(python_code_lines)
        return query_url, query_payload, python_code

    def generate_botify_bqlv1_python_code(self, query_payload: dict, username: str, project_name: str, 
                                        jobs_payload: dict, display_name: str, 
                                        get_step_name_from_payload_func) -> tuple:
        """Generate Python code for BQLv1 queries (web logs)."""
        
        # Get step name from payload
        step_name = get_step_name_from_payload_func(jobs_payload)
        
        # Extract date information from query payload
        start_date = query_payload.get('start_date', '2024-01-01')
        end_date = query_payload.get('end_date', '2024-12-31')
        
        # Generate the header
        header_lines = self.generate_botify_code_header(
            display_name=display_name,
            step_name=step_name,
            username=username,
            project_name=project_name
        )
        
        # Build the query URL for BQLv1 (web logs)
        query_url = f"https://app.botify.com/api/v1/logs/{username}/{project_name}/urls/{start_date}/{end_date}"
        query_url_with_params = f"{query_url}?page=1&size=100&sampling=100"
        
        # Extract the query body from the payload
        query_body = query_payload.get('query_body', query_payload.get('query', {}))
        
        # Generate the Python code
        python_code_lines = header_lines + [
            "",
            "import os",
            "import json",
            "import httpx",
            "from typing import Dict, Any",
            "",
            "# Configuration",
            "TOKEN_FILE = 'botify_token.txt'",
            "",
            self.generate_botify_token_loader(),
            "",
            "# API Configuration",
            "API_TOKEN = load_api_token()",
            f"URL = '{query_url_with_params}'",
            f"PAYLOAD = {json.dumps(query_body, indent=4)}",
            "",
            "def get_headers() -> Dict[str, str]:",
            "    return {",
            "        'Authorization': f'Token {API_TOKEN}',",
            "        'Content-Type': 'application/json'",
            "    }",
            "",
            self.generate_botify_http_client("make_bqlv1_query", "Execute BQLv1 query for web logs data"),
            "",
            self.generate_botify_main_executor("make_bqlv1_query", "BQLv1 Query")
        ]
        
        python_code = '\n'.join(python_code_lines)
        return query_url, query_payload, python_code

    def create_folder_button(self, folder_path: str, icon: str = "📁", text: str = "Open Folder", 
                           title_prefix: str = "Open folder"):
        """Generate a standardized folder opening button."""
        quoted_path = urllib.parse.quote(str(folder_path))
        title = f"{title_prefix}: {folder_path}"
        
        return A(
            f"{icon} {text}",
            hx_get=f"/open-folder?path={quoted_path}",
            hx_swap="none",
            title=title,
            cls="button-link"
        )

    def get_botify_analysis_path(self, app_name, username, project_name, analysis_slug, filename=None):
        """🚀 REUSABLE UTILITY: Construct standardized Botify analysis file paths"""
        base_path = Path.cwd() / 'downloads' / app_name / username / project_name / analysis_slug
        
        if filename:
            return base_path / filename
        return base_path

    def generate_botify_bqlv2_python_code(self, query_payload: dict, username: str, project_name: str, 
                                        page_size: int, jobs_payload: dict, display_name: str, 
                                        get_step_name_from_payload_func, get_configured_template_func=None, 
                                        query_templates=None) -> tuple:
        """Generate Python code for BQLv2 queries (crawl, GSC)."""
        # Generate the query URL
        query_url = f"https://api.botify.com/v1/projects/{username}/{project_name}/query"
        
        # Get step name from payload
        step_name = get_step_name_from_payload_func(jobs_payload)
        
        # Generate header
        template_info = {}
        if get_configured_template_func and query_templates:
            try:
                # Try to get template info for better header generation
                template_key = get_configured_template_func('analysis')  # Default to analysis template
                template_info = query_templates.get(template_key, {})
            except:
                pass
        
        header_lines = self.generate_botify_code_header(
            display_name=display_name,
            step_name=step_name,
            username=username,
            project_name=project_name,
            template_info=template_info
        )
        
        # Generate the complete Python code
        python_code = '''
import asyncio
import json
import os
from pathlib import Path
from typing import Dict, Any

import httpx

# Configuration
TOKEN_FILE = 'botify_token.txt'
URL = "{url}"
PAYLOAD = {payload}

{token_loader}

def get_headers() -> Dict[str, str]:
    """Get headers with API token."""
    api_token = load_api_token()
    return {{
        'Authorization': f'Token {{api_token}}',
        'Content-Type': 'application/json'
    }}

{http_client}

{main_executor}
'''.format(
            url=query_url,
            payload=json.dumps(query_payload, indent=4),
            token_loader=self.generate_botify_token_loader(),
            http_client=self.generate_botify_http_client('execute_bqlv2_query', 'Execute BQLv2 query for crawl/GSC data'),
            main_executor=self.generate_botify_main_executor('execute_bqlv2_query', 'BQLv2 Query')
        )
        
        # Combine header and code
        full_code = '\n'.join(header_lines) + '\n' + python_code
        
        return query_url, query_payload, full_code

    def generate_botify_bqlv1_python_code(self, query_payload: dict, username: str, project_name: str, 
                                        jobs_payload: dict, display_name: str, 
                                        get_step_name_from_payload_func) -> tuple:
        """Generate Python code for BQLv1 queries (web logs)."""
        # Generate the query URL
        query_url = f"https://app.botify.com/api/v1/logs/{username}/{project_name}/query"
        
        # Get step name from payload
        step_name = get_step_name_from_payload_func(jobs_payload)
        
        # Generate header
        header_lines = self.generate_botify_code_header(
            display_name=display_name,
            step_name=step_name,
            username=username,
            project_name=project_name
        )
        
        # Generate the Python code
        python_code = f'''
import httpx
import json
import asyncio
import os
from typing import Optional, Dict, Any

# Configuration
TOKEN_FILE = 'botify_token.txt'

{self.generate_botify_token_loader()}

# Configuration
API_TOKEN = load_api_token()
URL = "{query_url}"
PAYLOAD = {json.dumps(query_payload, indent=2)}

# Headers setup
def get_headers() -> Dict[str, str]:
    """Generate headers for the API request."""
    return {{
        'Authorization': f'Token {{API_TOKEN}}',
        'Content-Type': 'application/json'
    }}

{self.generate_botify_http_client('execute_bqlv1_query', 'Execute BQLv1 query for web logs analysis')}

{self.generate_botify_main_executor('execute_bqlv1_query', 'BQLv1 Web Logs Query')}
'''
        
        # Combine header and code
        full_code = '\n'.join(header_lines) + '\n' + python_code
        
        return query_url, query_payload, full_code

    async def execute_qualifier_logic(self, workflow_instance, username: str, project_name: str, 
                                    analysis_slug: str, api_token: str, qualifier_config: dict):
        """
        🎯 SHARED QUALIFIER LOGIC - Handles parameter optimization for all Botify workflows
        
        This method finds the optimal parameter value (like depth, page count, etc.) that stays 
        under API limits while maximizing data collection.
        """
        import httpx
        import json
        import asyncio
        
        # Extract configuration
        qualifier_template = qualifier_config['qualifier_bql_template']
        parameter_name = qualifier_config['iterative_parameter_name']
        target_metric_path = qualifier_config['target_metric_path']
        max_threshold = qualifier_config['max_value_threshold']
        iteration_range = qualifier_config['iteration_range']
        
        # Get messages
        user_message_running = qualifier_config.get('user_message_running', f'Finding optimal {parameter_name}...')
        user_message_found = qualifier_config.get('user_message_found', f'Optimal {parameter_name}: {{param_value}} (for {{metric_value:,}} results).')
        user_message_threshold_exceeded = qualifier_config.get('user_message_threshold_exceeded', f'{parameter_name.title()} count exceeds threshold even at minimum value.')
        
        # Add initial message
        await workflow_instance.message_queue.add(
            workflow_instance.pipulate,
            user_message_running,
            verbatim=True
        )
        
        # Build the base query
        base_query = {
            "collections": [f"crawl.{analysis_slug}"],
            "query": qualifier_template.copy()
        }
        
        # Replace collection placeholders
        def replace_iteration_placeholder(obj, placeholder, value):
            if isinstance(obj, dict):
                return {k: replace_iteration_placeholder(v, placeholder, value) for k, v in obj.items()}
            elif isinstance(obj, list):
                return [replace_iteration_placeholder(item, placeholder, value) for item in obj]
            elif isinstance(obj, str):
                return obj.replace(placeholder, str(value))
            return obj
        
        # Replace collection placeholder first
        base_query = replace_iteration_placeholder(base_query, '{collection}', f'crawl.{analysis_slug}')
        
        # Iterate through parameter values
        start_val, end_val, step_val = iteration_range
        optimal_param_value = start_val
        final_metric_value = 0
        
        first_call_logged = False
        
        for current_iter_val in range(start_val, end_val + 1, step_val):
            # Replace iteration placeholder
            query_payload = replace_iteration_placeholder(base_query, '{ITERATION_VALUE}', current_iter_val)
            
            # Make API call
            url = f'https://api.botify.com/v1/projects/{username}/{project_name}/query'
            headers = {'Authorization': f'Token {api_token}', 'Content-Type': 'application/json'}
            
            try:
                async with httpx.AsyncClient() as client:
                    response = await client.post(url, headers=headers, json=query_payload, timeout=60.0)
                    response.raise_for_status()
                    result = response.json()
                
                # Log first API call for transparency
                if not first_call_logged:
                    # Generate curl and python commands for transparency
                    curl_cmd = f"curl -X POST '{url}' -H 'Authorization: Token {{YOUR_BOTIFY_API_TOKEN}}' -H 'Content-Type: application/json' -d '{json.dumps(query_payload)}'"
                    python_cmd = f"""
import httpx
import json

async def test_qualifier():
    url = "{url}"
    headers = {repr(headers)}
    payload = {json.dumps(query_payload, indent=2)}
    
    async with httpx.AsyncClient() as client:
        response = await client.post(url, headers=headers, json=payload, timeout=60.0)
        response.raise_for_status()
        return response.json()

await test_qualifier()
"""
                    
                    # Use workflow instance's database instead of pip.db
                    await workflow_instance.pipulate.log_api_call_details(
                        pipeline_id=workflow_instance.db.get('pipeline_id', 'unknown'),
                        step_id='depth_finding',
                        call_description=f'First Depth Finding Query - {parameter_name}={current_iter_val}',
                        method='POST',
                        url=url,
                        headers=headers,
                        payload=query_payload,
                        response_status=response.status_code,
                        curl_command=curl_cmd,
                        python_command=python_cmd,
                        notes=f'Finding optimal {parameter_name} value (showing first query only to avoid spam)'
                    )
                    first_call_logged = True
                
                # Extract metric value from result
                metric_value = result
                for path_segment in target_metric_path:
                    if isinstance(metric_value, dict):
                        metric_value = metric_value.get(path_segment, 0)
                    elif isinstance(metric_value, list) and isinstance(path_segment, int):
                        metric_value = metric_value[path_segment] if path_segment < len(metric_value) else 0
                    else:
                        metric_value = 0
                        break
                
                # Update status message
                await workflow_instance.message_queue.add(
                    workflow_instance.pipulate,
                    f'🔍 Qualifier \'{parameter_name}\' at {current_iter_val}: {metric_value:,} items.',
                    verbatim=True
                )
                
                # Check if we're still under threshold
                if metric_value <= max_threshold:
                    optimal_param_value = current_iter_val
                    final_metric_value = metric_value
                else:
                    # Exceeded threshold, use previous value
                    break
                    
            except Exception as e:
                await workflow_instance.message_queue.add(
                    workflow_instance.pipulate,
                    f'Error during qualifier check at {parameter_name}={current_iter_val}: {str(e)}',
                    verbatim=True
                )
                # Continue with current optimal value
                break
        
        # Add final message
        final_message = user_message_found.format(param_value=optimal_param_value, metric_value=final_metric_value)
        await workflow_instance.message_queue.add(
            workflow_instance.pipulate,
            final_message,
            verbatim=True
        )
        
        return optimal_param_value, final_metric_value

# Convenience instance for backward compatibility with existing plugins
botify_code_generators = BotifyCodeGenerators()

# Export individual functions for easy importing
generate_botify_code_header = botify_code_generators.generate_botify_code_header
generate_botify_token_loader = botify_code_generators.generate_botify_token_loader
generate_botify_http_client = botify_code_generators.generate_botify_http_client
generate_botify_main_executor = botify_code_generators.generate_botify_main_executor
generate_botify_bqlv2_python_code = botify_code_generators.generate_botify_bqlv2_python_code
generate_botify_bqlv1_python_code = botify_code_generators.generate_botify_bqlv1_python_code
create_folder_button = botify_code_generators.create_folder_button
get_botify_analysis_path = botify_code_generators.get_botify_analysis_path
generate_botify_bqlv2_python_code = botify_code_generators.generate_botify_bqlv2_python_code
generate_botify_bqlv1_python_code = botify_code_generators.generate_botify_bqlv1_python_code
execute_qualifier_logic = botify_code_generators.execute_qualifier_logic
