"""Parser for ComfyUI metadata format."""

import re
import json
import logging
from typing import Dict, Any
from ..base import RecipeMetadataParser
from ..constants import GEN_PARAM_KEYS
from ...services.metadata_service import get_default_metadata_provider

logger = logging.getLogger(__name__)

class ComfyMetadataParser(RecipeMetadataParser):
    """Parser for Civitai ComfyUI metadata JSON format"""
    
    METADATA_MARKER = r"class_type"
    
    def is_metadata_matching(self, user_comment: str) -> bool:
        """Check if the user comment matches the ComfyUI metadata format"""
        try:
            data = json.loads(user_comment)
            # Check if it contains class_type nodes typical of ComfyUI workflow
            return isinstance(data, dict) and any(isinstance(v, dict) and 'class_type' in v for v in data.values())
        except (json.JSONDecodeError, TypeError):
            return False
    
    async def parse_metadata(self, user_comment: str, recipe_scanner=None, civitai_client=None) -> Dict[str, Any]:
        """Parse metadata from Civitai ComfyUI metadata format"""
        try:
            # Get metadata provider instead of using civitai_client directly
            metadata_provider = await get_default_metadata_provider()
            
            data = json.loads(user_comment)

            checkpoint_nodes = {k: v for k, v in data.items() if isinstance(v, dict) and v.get('class_type') == 'CheckpointLoaderSimple'}
            checkpoint = None
            checkpoint_id = None
            checkpoint_version_id = None
            if checkpoint_nodes:
                checkpoint_node = next(iter(checkpoint_nodes.values()))
                if 'inputs' in checkpoint_node and 'ckpt_name' in checkpoint_node['inputs']:
                    checkpoint_name = checkpoint_node['inputs']['ckpt_name']
                    # Some ComfyUI workflows serialize ckpt_name as a
                    # single-element list (e.g. ["model.safetensors"]) or leave
                    # the value unset (None). Neither is a string, so skip the
                    # CivitAI-URN lookup instead of crashing re.search with a
                    # TypeError that fails the whole image import.
                    if isinstance(checkpoint_name, list):
                        checkpoint_name = (
                            checkpoint_name[0] if checkpoint_name else None
                        )
                    if isinstance(checkpoint_name, str):
                        checkpoint_match = re.search(r'civitai:(\d+)@(\d+)', checkpoint_name)
                        if checkpoint_match:
                            checkpoint_id = checkpoint_match.group(1)
                            checkpoint_version_id = checkpoint_match.group(2)
                            checkpoint = {
                                'id': checkpoint_version_id,
                                'modelId': checkpoint_id,
                                'name': f"Checkpoint {checkpoint_id}",
                                'version': '',
                                'type': 'checkpoint'
                            }
                            if metadata_provider:
                                try:
                                    civitai_info_tuple = await metadata_provider.get_model_version_info(checkpoint_version_id)
                                    civitai_info, _ = civitai_info_tuple if isinstance(civitai_info_tuple, tuple) else (civitai_info_tuple, None)
                                    checkpoint = await self.populate_checkpoint_from_civitai(checkpoint, civitai_info)
                                except Exception as e:
                                    logger.error(f"Error fetching Civitai info for checkpoint: {e}")

            recipe_base_model = checkpoint.get('baseModel') if checkpoint else None
            loras = []
            lora_candidates = []
            for node in data.values():
                if not isinstance(node, dict):
                    continue

                inputs = node.get('inputs')
                if not isinstance(inputs, dict):
                    continue

                if node.get('class_type') == 'LoraLoader':
                    lora_name = inputs.get('lora_name', '')
                    if isinstance(lora_name, str) and lora_name:
                        lora_candidates.append((lora_name, inputs.get('strength_model', 1.0)))
                    continue

                if node.get('class_type') != 'LoraLoaderLM':
                    continue

                loras_data = inputs.get('loras', [])
                if isinstance(loras_data, dict):
                    loras_data = loras_data.get('__value__', [])
                if isinstance(loras_data, list) and len(loras_data) == 1 and isinstance(loras_data[0], list):
                    loras_data = loras_data[0]
                if not isinstance(loras_data, list):
                    continue

                for lora in loras_data:
                    if not isinstance(lora, dict) or not lora.get('active', False) or lora.get('_isDummy', False):
                        continue
                    lora_name = lora.get('name', '')
                    if isinstance(lora_name, str) and lora_name:
                        lora_candidates.append((lora_name, lora.get('strength', 1.0)))

            for lora_name, weight in lora_candidates:
                if isinstance(weight, str):
                    try:
                        weight = float(weight)
                    except ValueError:
                        weight = 1.0
                lora_id_match = re.search(r'civitai:(\d+)@(\d+)', lora_name)
                if lora_id_match:
                    model_id = lora_id_match.group(1)
                    model_version_id = lora_id_match.group(2)
                    entry_name = f"Lora {model_id}"
                else:
                    model_id = 0
                    model_version_id = 0
                    entry_name = re.split(r'[\\/]', lora_name)[-1]
                    entry_name = re.sub(r'\.[^.]+$', '', entry_name)

                lora_entry = {
                    'id': model_version_id,
                    'modelId': model_id,
                    'name': entry_name,
                    'version': '',
                    'type': 'lora',
                    'weight': weight,
                    'existsLocally': False,
                    'localPath': None,
                    'file_name': entry_name,
                    'hash': '',
                    'thumbnailUrl': '/loras_static/images/no-preview.png',
                    'baseModel': '',
                    'size': 0,
                    'downloadUrl': '',
                    'isDeleted': False
                }

                if lora_id_match:
                    if metadata_provider:
                        try:
                            civitai_info_tuple = await metadata_provider.get_model_version_info(model_version_id)
                            populated_entry = await self.populate_lora_from_civitai(
                                lora_entry,
                                civitai_info_tuple,
                                recipe_scanner
                            )
                            if populated_entry is None:
                                continue
                            lora_entry = populated_entry
                        except Exception as e:
                            logger.error(f"Error fetching Civitai info for LoRA: {e}")
                else:
                    if not recipe_scanner:
                        continue
                    local_lora = await recipe_scanner.get_local_lora(lora_name, recipe_base_model)
                    if not local_lora:
                        continue
                    lora_entry = self.populate_lora_from_local(lora_entry, local_lora)

                loras.append(lora_entry)
            
            # Extract generation parameters
            gen_params = {}
            
            # First try to get from extraMetadata
            if 'extraMetadata' in data:
                try:
                    # extraMetadata is a JSON string that needs to be parsed
                    extra_metadata = json.loads(data['extraMetadata'])
                    
                    # Map fields from extraMetadata to our standard format
                    mapping = {
                        'prompt': 'prompt',
                        'negativePrompt': 'negative_prompt',
                        'steps': 'steps',
                        'sampler': 'sampler',
                        'cfgScale': 'cfg_scale',
                        'seed': 'seed'
                    }
                    
                    for src_key, dest_key in mapping.items():
                        if src_key in extra_metadata:
                            gen_params[dest_key] = extra_metadata[src_key]
                    
                    # If size info is available, format as "width x height"
                    if 'width' in extra_metadata and 'height' in extra_metadata:
                        gen_params['size'] = f"{extra_metadata['width']}x{extra_metadata['height']}"
                    
                except Exception as e:
                    logger.error(f"Error parsing extraMetadata: {e}")
            
            # If extraMetadata doesn't have all the info, try to get from nodes
            if not gen_params or len(gen_params) < 3:  # At least we want prompt, negative_prompt, and steps
                # Find positive prompt node
                positive_nodes = {k: v for k, v in data.items() if isinstance(v, dict) and 
                                v.get('class_type', '').endswith('CLIPTextEncode') and 
                                v.get('_meta', {}).get('title') == 'Positive'}
                
                if positive_nodes:
                    positive_node = next(iter(positive_nodes.values()))
                    if 'inputs' in positive_node and 'text' in positive_node['inputs']:
                        gen_params['prompt'] = positive_node['inputs']['text']
                
                # Find negative prompt node
                negative_nodes = {k: v for k, v in data.items() if isinstance(v, dict) and 
                                v.get('class_type', '').endswith('CLIPTextEncode') and 
                                v.get('_meta', {}).get('title') == 'Negative'}
                
                if negative_nodes:
                    negative_node = next(iter(negative_nodes.values()))
                    if 'inputs' in negative_node and 'text' in negative_node['inputs']:
                        gen_params['negative_prompt'] = negative_node['inputs']['text']
                
                # Find KSampler node for other parameters
                ksampler_nodes = {k: v for k, v in data.items() if isinstance(v, dict) and v.get('class_type') == 'KSampler'}
                
                if ksampler_nodes:
                    ksampler_node = next(iter(ksampler_nodes.values()))
                    if 'inputs' in ksampler_node:
                        inputs = ksampler_node['inputs']
                        if 'sampler_name' in inputs:
                            gen_params['sampler'] = inputs['sampler_name']
                        if 'steps' in inputs:
                            gen_params['steps'] = inputs['steps']
                        if 'cfg' in inputs:
                            gen_params['cfg_scale'] = inputs['cfg']
                        if 'seed' in inputs:
                            gen_params['seed'] = inputs['seed']
            
            # Determine base model from loras info
            base_model = None
            if loras:
                # Use the most common base model from loras
                base_models = [lora['baseModel'] for lora in loras if lora.get('baseModel')]
                if base_models:
                    from collections import Counter
                    base_model_counts = Counter(base_models)
                    base_model = base_model_counts.most_common(1)[0][0]
            
            return {
                'base_model': base_model,
                'loras': loras,
                'checkpoint': checkpoint,
                'gen_params': gen_params,
                'from_comfy_metadata': True
            }
            
        except Exception as e:
            logger.error(f"Error parsing ComfyUI metadata: {e}", exc_info=True)
            return {"error": str(e), "loras": []}
