import json
import random
import logging

from models import db, Character, Checkpoint, Preset
from services.prompts import build_prompt, _dedup_tags
from services.workflow import _prepare_workflow, _get_default_checkpoint
from services.job_queue import _enqueue_job, _make_finalize
from services.sync import _resolve_preset_entity, _resolve_preset_fields

logger = logging.getLogger('gaze')


def generate_from_preset(preset, overrides=None, save_category='presets'):
    """Execute preset-based generation.

    Args:
        preset: Preset ORM object
        overrides: optional dict with keys:
            checkpoint, extra_positive, extra_negative, seed, width, height, action
        save_category: upload sub-directory ('presets' or 'generator')

    Returns:
        job dict from _enqueue_job()
    """
    if overrides is None:
        overrides = {}

    action = overrides.get('action', 'preview')
    extra_positive = overrides.get('extra_positive', '').strip()
    extra_negative = overrides.get('extra_negative', '').strip()

    data = preset.data

    # Resolve entities
    char_cfg = data.get('character', {})
    character = _resolve_preset_entity('character', char_cfg.get('character_id'))
    if not character:
        character = Character.query.order_by(db.func.random()).first()

    outfit_cfg = data.get('outfit', {})
    action_cfg = data.get('action', {})
    style_cfg = data.get('style', {})
    scene_cfg = data.get('scene', {})
    detailer_cfg = data.get('detailer', {})
    look_cfg = data.get('look', {})
    ckpt_cfg = data.get('checkpoint', {})

    outfit = _resolve_preset_entity('outfit', outfit_cfg.get('outfit_id'))
    action_obj = _resolve_preset_entity('action', action_cfg.get('action_id'))
    style_obj = _resolve_preset_entity('style', style_cfg.get('style_id'))
    scene_obj = _resolve_preset_entity('scene', scene_cfg.get('scene_id'))
    detailer_obj = _resolve_preset_entity('detailer', detailer_cfg.get('detailer_id'))
    look_obj = _resolve_preset_entity('look', look_cfg.get('look_id'))

    # Build sidecar metadata with resolved entity slugs
    resolved_meta = {
        'preset_slug': preset.slug,
        'preset_name': preset.name,
        'character_slug': character.slug if character else None,
        'outfit_slug': outfit.slug if outfit else None,
        'action_slug': action_obj.slug if action_obj else None,
        'style_slug': style_obj.slug if style_obj else None,
        'scene_slug': scene_obj.slug if scene_obj else None,
        'detailer_slug': detailer_obj.slug if detailer_obj else None,
        'look_slug': look_obj.slug if look_obj else None,
    }

    # Checkpoint: override > preset config > default
    checkpoint_override = overrides.get('checkpoint', '').strip() if overrides.get('checkpoint') else ''
    if checkpoint_override:
        ckpt_obj = Checkpoint.query.filter_by(checkpoint_path=checkpoint_override).first()
        ckpt_path = checkpoint_override
        ckpt_data = ckpt_obj.data if ckpt_obj else None
    else:
        preset_ckpt = ckpt_cfg.get('checkpoint_path')
        if preset_ckpt == 'random':
            ckpt_obj = Checkpoint.query.order_by(db.func.random()).first()
            ckpt_path = ckpt_obj.checkpoint_path if ckpt_obj else None
            ckpt_data = ckpt_obj.data if ckpt_obj else None
        elif preset_ckpt:
            ckpt_obj = Checkpoint.query.filter_by(checkpoint_path=preset_ckpt).first()
            ckpt_path = preset_ckpt
            ckpt_data = ckpt_obj.data if ckpt_obj else None
        else:
            ckpt_path, ckpt_data = _get_default_checkpoint()

    resolved_meta['checkpoint_path'] = ckpt_path

    # Resolve selected fields from preset toggles
    selected_fields = _resolve_preset_fields(data)

    # Check suppress_wardrobe: preset override > action default
    suppress_wardrobe = False
    preset_suppress = action_cfg.get('suppress_wardrobe')
    if preset_suppress == 'random':
        suppress_wardrobe = random.choice([True, False])
    elif preset_suppress is not None:
        suppress_wardrobe = bool(preset_suppress)
    elif action_obj:
        suppress_wardrobe = action_obj.data.get('suppress_wardrobe', False)

    if suppress_wardrobe:
        selected_fields = [f for f in selected_fields if not f.startswith('wardrobe::')]

    # Build combined data for prompt building
    active_wardrobe = char_cfg.get('fields', {}).get('wardrobe', {}).get('outfit', 'default')
    wardrobe_source = outfit.data.get('wardrobe', {}) if outfit else None
    if wardrobe_source is None:
        wardrobe_source = character.get_active_wardrobe() if character else {}
    if suppress_wardrobe:
        wardrobe_source = {}

    combined_data = {
        'character_id': character.character_id if character else 'unknown',
        'identity': character.data.get('identity', {}) if character else {},
        'defaults': character.data.get('defaults', {}) if character else {},
        'wardrobe': wardrobe_source,
        'styles': character.data.get('styles', {}) if character else {},
        'lora': (look_obj.data.get('lora', {}) if look_obj
                 else (character.data.get('lora', {}) if character else {})),
    }

    # Build extras prompt from secondary resources
    extras_parts = []
    if action_obj:
        action_fields = action_cfg.get('fields', {})
        from utils import _BODY_GROUP_KEYS
        for key in _BODY_GROUP_KEYS:
            val_cfg = action_fields.get(key, True)
            if val_cfg == 'random':
                val_cfg = random.choice([True, False])
            if val_cfg:
                val = action_obj.data.get('action', {}).get(key, '')
                if val:
                    extras_parts.append(val)
        if action_cfg.get('use_lora', True):
            trg = action_obj.data.get('lora', {}).get('lora_triggers', '')
            if trg:
                extras_parts.append(trg)
    if style_obj:
        s = style_obj.data.get('style', {})
        if s.get('artist_name'):
            extras_parts.append(f"by {s['artist_name']}")
        if s.get('artistic_style'):
            extras_parts.append(s['artistic_style'])
        if style_cfg.get('use_lora', True):
            trg = style_obj.data.get('lora', {}).get('lora_triggers', '')
            if trg:
                extras_parts.append(trg)
    if scene_obj:
        scene_fields = scene_cfg.get('fields', {})
        for key in ['background', 'foreground', 'furniture', 'colors', 'lighting', 'theme']:
            val_cfg = scene_fields.get(key, True)
            if val_cfg == 'random':
                val_cfg = random.choice([True, False])
            if val_cfg:
                val = scene_obj.data.get('scene', {}).get(key, '')
                if val:
                    extras_parts.append(val)
        if scene_cfg.get('use_lora', True):
            trg = scene_obj.data.get('lora', {}).get('lora_triggers', '')
            if trg:
                extras_parts.append(trg)
    if detailer_obj:
        prompt_val = detailer_obj.data.get('prompt', '')
        if isinstance(prompt_val, list):
            extras_parts.extend(p for p in prompt_val if p)
        elif prompt_val:
            extras_parts.append(prompt_val)
        if detailer_cfg.get('use_lora', True):
            trg = detailer_obj.data.get('lora', {}).get('lora_triggers', '')
            if trg:
                extras_parts.append(trg)

    with open('comfy_workflow.json', 'r') as f:
        workflow = json.load(f)

    prompts = build_prompt(combined_data, selected_fields, default_fields=None,
                           active_outfit=active_wardrobe)
    if extras_parts:
        extra_str = ', '.join(filter(None, extras_parts))
        prompts['main'] = _dedup_tags(f"{prompts['main']}, {extra_str}" if prompts['main'] else extra_str)

    if extra_positive:
        prompts["main"] = f"{prompts['main']}, {extra_positive}"

    # Parse optional seed
    fixed_seed = overrides.get('seed')
    if fixed_seed is not None:
        fixed_seed = int(fixed_seed)

    # Resolution: override > preset config > workflow default
    res_cfg = data.get('resolution', {})
    override_width = overrides.get('width')
    override_height = overrides.get('height')
    if override_width and override_height:
        gen_width = int(override_width)
        gen_height = int(override_height)
    elif res_cfg.get('random', False):
        _RES_OPTIONS = [
            (1024, 1024), (1152, 896), (896, 1152), (1344, 768),
            (768, 1344), (1280, 800), (800, 1280),
        ]
        gen_width, gen_height = random.choice(_RES_OPTIONS)
    else:
        gen_width = res_cfg.get('width') or None
        gen_height = res_cfg.get('height') or None

    workflow = _prepare_workflow(
        workflow, character, prompts,
        checkpoint=ckpt_path, checkpoint_data=ckpt_data,
        custom_negative=extra_negative or None,
        outfit=outfit if outfit_cfg.get('use_lora', True) else None,
        action=action_obj if action_cfg.get('use_lora', True) else None,
        style=style_obj if style_cfg.get('use_lora', True) else None,
        scene=scene_obj if scene_cfg.get('use_lora', True) else None,
        detailer=detailer_obj if detailer_cfg.get('use_lora', True) else None,
        look=look_obj,
        fixed_seed=fixed_seed,
        width=gen_width,
        height=gen_height,
    )

    label = f"Preset: {preset.name} – {action}"
    db_model = Preset if save_category == 'presets' else None
    job = _enqueue_job(label, workflow, _make_finalize(save_category, preset.slug, db_model, action, metadata=resolved_meta))

    return job
