"""
Shared route factory functions for common patterns across all resource categories.

Each factory registers a route on the Flask app using the closure pattern,
preserving endpoint names for url_for() compatibility.
"""

import json
import logging
import os
import re

from flask import current_app, flash, redirect, request, url_for
from sqlalchemy.orm.attributes import flag_modified
from werkzeug.utils import secure_filename

from models import db
from utils import allowed_file

logger = logging.getLogger('gaze')


def apply_library_filters(query, model_class):
    """Apply standard favourite/NSFW filters and sorting to a library query.

    Returns (items, favourite_filter, nsfw_filter) tuple.
    """
    fav = request.args.get('favourite')
    nsfw = request.args.get('nsfw', 'all')
    if fav == 'on':
        query = query.filter_by(is_favourite=True)
    if nsfw == 'sfw':
        query = query.filter_by(is_nsfw=False)
    elif nsfw == 'nsfw':
        query = query.filter_by(is_nsfw=True)
    items = query.order_by(model_class.is_favourite.desc(), model_class.name).all()
    return items, fav or '', nsfw


# ---------------------------------------------------------------------------
# Category configuration registry
# ---------------------------------------------------------------------------
# Each entry maps a category name to its metadata.
# 'url_prefix': the URL segment (e.g. 'outfit' → /outfit/<slug>/...)
# 'detail_endpoint': the Flask endpoint name for the detail page
# 'config_dir': the app.config key for the JSON data directory
# 'category_folder': subfolder under static/uploads/
# 'id_field': JSON key for entity ID
# 'name_field': JSON key for display name

CATEGORY_CONFIG = {
    'characters': {
        'model': None,  # Set at import time to avoid circular imports
        'url_prefix': 'character',
        'detail_endpoint': 'detail',
        'config_dir': 'CHARACTERS_DIR',
        'category_folder': 'characters',
        'id_field': 'character_id',
        'name_field': 'character_name',
        # Characters use unprefixed endpoint names
        'endpoint_prefix': '',
    },
    'outfits': {
        'model': None,
        'url_prefix': 'outfit',
        'detail_endpoint': 'outfit_detail',
        'config_dir': 'CLOTHING_DIR',
        'category_folder': 'outfits',
        'id_field': 'outfit_id',
        'name_field': 'outfit_name',
        'endpoint_prefix': 'outfit_',
    },
    'actions': {
        'model': None,
        'url_prefix': 'action',
        'detail_endpoint': 'action_detail',
        'config_dir': 'ACTIONS_DIR',
        'category_folder': 'actions',
        'id_field': 'action_id',
        'name_field': 'action_name',
        'endpoint_prefix': 'action_',
    },
    'styles': {
        'model': None,
        'url_prefix': 'style',
        'detail_endpoint': 'style_detail',
        'config_dir': 'STYLES_DIR',
        'category_folder': 'styles',
        'id_field': 'style_id',
        'name_field': 'style_name',
        'endpoint_prefix': 'style_',
    },
    'scenes': {
        'model': None,
        'url_prefix': 'scene',
        'detail_endpoint': 'scene_detail',
        'config_dir': 'SCENES_DIR',
        'category_folder': 'scenes',
        'id_field': 'scene_id',
        'name_field': 'scene_name',
        'endpoint_prefix': 'scene_',
    },
    'detailers': {
        'model': None,
        'url_prefix': 'detailer',
        'detail_endpoint': 'detailer_detail',
        'config_dir': 'DETAILERS_DIR',
        'category_folder': 'detailers',
        'id_field': 'detailer_id',
        'name_field': 'detailer_name',
        'endpoint_prefix': 'detailer_',
    },
    'looks': {
        'model': None,
        'url_prefix': 'look',
        'detail_endpoint': 'look_detail',
        'config_dir': 'LOOKS_DIR',
        'category_folder': 'looks',
        'id_field': 'look_id',
        'name_field': 'look_name',
        'endpoint_prefix': 'look_',
    },
    'checkpoints': {
        'model': None,
        'url_prefix': 'checkpoint',
        'detail_endpoint': 'checkpoint_detail',
        'config_dir': 'CHECKPOINTS_DIR',
        'category_folder': 'checkpoints',
        'id_field': 'checkpoint_path',
        'name_field': 'checkpoint_name',
        'endpoint_prefix': 'checkpoint_',
    },
    'presets': {
        'model': None,
        'url_prefix': 'preset',
        'detail_endpoint': 'preset_detail',
        'config_dir': 'PRESETS_DIR',
        'category_folder': 'presets',
        'id_field': 'preset_id',
        'name_field': 'preset_name',
        'endpoint_prefix': 'preset_',
    },
}


def _init_models():
    """Lazily populate model references to avoid circular imports."""
    from models import (Action, Character, Checkpoint, Detailer, Look,
                        Outfit, Preset, Scene, Style)
    CATEGORY_CONFIG['characters']['model'] = Character
    CATEGORY_CONFIG['outfits']['model'] = Outfit
    CATEGORY_CONFIG['actions']['model'] = Action
    CATEGORY_CONFIG['styles']['model'] = Style
    CATEGORY_CONFIG['scenes']['model'] = Scene
    CATEGORY_CONFIG['detailers']['model'] = Detailer
    CATEGORY_CONFIG['looks']['model'] = Look
    CATEGORY_CONFIG['checkpoints']['model'] = Checkpoint
    CATEGORY_CONFIG['presets']['model'] = Preset


def _get_config(category):
    """Get config for a category, initializing models if needed."""
    cfg = CATEGORY_CONFIG[category]
    if cfg['model'] is None:
        _init_models()
    return cfg


# ---------------------------------------------------------------------------
# Factory functions
# ---------------------------------------------------------------------------

def _register_favourite_route(app, cfg):
    """Register POST /<prefix>/<slug>/favourite toggle route."""
    Model = cfg['model']
    prefix = cfg['url_prefix']
    detail_ep = cfg['detail_endpoint']
    ep_prefix = cfg['endpoint_prefix']

    # Characters use 'toggle_character_favourite', others use 'toggle_{prefix}_favourite'
    if ep_prefix == '':
        endpoint_name = 'toggle_character_favourite'
    else:
        endpoint_name = f'toggle_{prefix}_favourite'

    @app.route(f'/{prefix}/<path:slug>/favourite', methods=['POST'],
               endpoint=endpoint_name)
    def favourite_toggle(slug):
        entity = Model.query.filter_by(slug=slug).first_or_404()
        entity.is_favourite = not entity.is_favourite
        db.session.commit()
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return {'success': True, 'is_favourite': entity.is_favourite}
        return redirect(url_for(detail_ep, slug=slug))


def _register_upload_route(app, cfg):
    """Register POST /<prefix>/<slug>/upload image route."""
    Model = cfg['model']
    prefix = cfg['url_prefix']
    detail_ep = cfg['detail_endpoint']
    ep_prefix = cfg['endpoint_prefix']
    folder = cfg['category_folder']

    if ep_prefix == '':
        endpoint_name = 'upload_image'
    else:
        endpoint_name = f'upload_{prefix}_image'

    @app.route(f'/{prefix}/<path:slug>/upload', methods=['POST'],
               endpoint=endpoint_name)
    def upload_image(slug):
        entity = Model.query.filter_by(slug=slug).first_or_404()

        if 'image' not in request.files:
            flash('No file part')
            return redirect(request.url)

        file = request.files['image']
        if file.filename == '':
            flash('No selected file')
            return redirect(request.url)

        if file and allowed_file(file.filename):
            entity_folder = os.path.join(
                current_app.config['UPLOAD_FOLDER'], f"{folder}/{slug}")
            os.makedirs(entity_folder, exist_ok=True)

            filename = secure_filename(file.filename)
            file_path = os.path.join(entity_folder, filename)
            file.save(file_path)

            entity.image_path = f"{folder}/{slug}/{filename}"
            db.session.commit()
            flash('Image uploaded successfully!')

        return redirect(url_for(detail_ep, slug=slug))


def _register_replace_cover_route(app, cfg):
    """Register POST /<prefix>/<slug>/replace_cover_from_preview route."""
    Model = cfg['model']
    prefix = cfg['url_prefix']
    detail_ep = cfg['detail_endpoint']
    ep_prefix = cfg['endpoint_prefix']

    if ep_prefix == '':
        endpoint_name = 'replace_cover_from_preview'
    else:
        endpoint_name = f'replace_{prefix}_cover_from_preview'

    @app.route(f'/{prefix}/<path:slug>/replace_cover_from_preview',
               methods=['POST'], endpoint=endpoint_name)
    def replace_cover(slug):
        entity = Model.query.filter_by(slug=slug).first_or_404()
        preview_path = request.form.get('preview_path')
        if preview_path:
            full_path = os.path.realpath(
                os.path.join(current_app.config['UPLOAD_FOLDER'], preview_path))
            upload_root = os.path.realpath(current_app.config['UPLOAD_FOLDER'])
            if full_path.startswith(upload_root + os.sep) and os.path.exists(full_path):
                entity.image_path = preview_path
                db.session.commit()
                flash('Cover image updated!')
            else:
                flash('Invalid preview path.', 'error')
        else:
            flash('No valid preview image selected.', 'error')
        return redirect(url_for(detail_ep, slug=slug))


def _register_save_defaults_route(app, cfg):
    """Register POST /<prefix>/<slug>/save_defaults route."""
    Model = cfg['model']
    prefix = cfg['url_prefix']
    detail_ep = cfg['detail_endpoint']
    ep_prefix = cfg['endpoint_prefix']
    category = cfg['category_folder']

    if ep_prefix == '':
        endpoint_name = 'save_defaults'
    else:
        endpoint_name = f'save_{prefix}_defaults'

    # Display name for the flash message
    display = category.rstrip('s')

    @app.route(f'/{prefix}/<path:slug>/save_defaults', methods=['POST'],
               endpoint=endpoint_name)
    def save_defaults(slug):
        entity = Model.query.filter_by(slug=slug).first_or_404()
        selected_fields = request.form.getlist('include_field')
        entity.default_fields = selected_fields
        db.session.commit()
        flash(f'Default prompt selection saved for this {display}!')
        return redirect(url_for(detail_ep, slug=slug))


def _register_clone_route(app, cfg):
    """Register POST /<prefix>/<slug>/clone route."""
    Model = cfg['model']
    prefix = cfg['url_prefix']
    detail_ep = cfg['detail_endpoint']
    config_dir = cfg['config_dir']
    id_field = cfg['id_field']
    name_field = cfg['name_field']

    endpoint_name = f'clone_{prefix}'

    @app.route(f'/{prefix}/<path:slug>/clone', methods=['POST'],
               endpoint=endpoint_name)
    def clone_entity(slug):
        entity = Model.query.filter_by(slug=slug).first_or_404()

        base_id = getattr(entity, id_field, None) or entity.data.get(id_field)
        match = re.match(r'^(.+?)_(\d+)$', base_id)
        if match:
            base_name = match.group(1)
            current_num = int(match.group(2))
        else:
            base_name = base_id
            current_num = 1

        next_num = current_num + 1
        while True:
            new_id = f"{base_name}_{next_num:02d}"
            new_filename = f"{new_id}.json"
            new_path = os.path.join(
                current_app.config[config_dir], new_filename)
            if not os.path.exists(new_path):
                break
            next_num += 1

        new_data = dict(entity.data)
        new_data[id_field] = new_id
        new_data[name_field] = f"{entity.name} (Copy)"

        with open(new_path, 'w') as f:
            json.dump(new_data, f, indent=2)

        new_slug = re.sub(r'[^a-zA-Z0-9_]', '', new_id)
        kwargs = {
            id_field: new_id,
            'slug': new_slug,
            'filename': new_filename,
            'name': new_data[name_field],
            'data': new_data,
        }
        new_entity = Model(**kwargs)
        db.session.add(new_entity)
        db.session.commit()

        flash(f'Cloned as "{new_id}"!')
        return redirect(url_for(detail_ep, slug=new_slug))


def _register_save_json_route(app, cfg):
    """Register POST /<prefix>/<slug>/save_json route."""
    Model = cfg['model']
    prefix = cfg['url_prefix']
    config_dir = cfg['config_dir']
    ep_prefix = cfg['endpoint_prefix']

    if ep_prefix == '':
        endpoint_name = 'save_character_json'
    else:
        endpoint_name = f'save_{prefix}_json'

    @app.route(f'/{prefix}/<path:slug>/save_json', methods=['POST'],
               endpoint=endpoint_name)
    def save_json(slug):
        entity = Model.query.filter_by(slug=slug).first_or_404()
        try:
            new_data = json.loads(request.form.get('json_data', ''))
        except (ValueError, TypeError) as e:
            return {'success': False, 'error': f'Invalid JSON: {e}'}, 400
        entity.data = new_data
        flag_modified(entity, 'data')
        db.session.commit()
        if entity.filename:
            file_path = os.path.join(
                current_app.config[config_dir], entity.filename)
            with open(file_path, 'w') as f:
                json.dump(new_data, f, indent=2)
        return {'success': True}


def _register_get_missing_route(app, cfg):
    """Register GET /get_missing_<category> route."""
    Model = cfg['model']
    category = cfg['category_folder']

    endpoint_name = f'get_missing_{category}'

    @app.route(f'/get_missing_{category}', endpoint=endpoint_name)
    def get_missing():
        missing = Model.query.filter(
            (Model.image_path == None) | (Model.image_path == '')
        ).order_by(Model.name).all()
        return {'missing': [{'slug': e.slug, 'name': e.name} for e in missing]}


def _register_clear_covers_route(app, cfg):
    """Register POST /clear_all_<category>_covers route."""
    Model = cfg['model']
    category = cfg['category_folder']
    ep_prefix = cfg['endpoint_prefix']

    # Characters use 'clear_all_covers', others use 'clear_all_{category}_covers'
    if ep_prefix == '':
        endpoint_name = 'clear_all_covers'
        url = '/clear_all_covers'
    else:
        endpoint_name = f'clear_all_{category.rstrip("s")}_covers'
        url = f'/clear_all_{category.rstrip("s")}_covers'

    @app.route(url, methods=['POST'], endpoint=endpoint_name)
    def clear_covers():
        entities = Model.query.all()
        for entity in entities:
            entity.image_path = None
        db.session.commit()
        return {'success': True}


# ---------------------------------------------------------------------------
# Main registration function
# ---------------------------------------------------------------------------

def register_common_routes(app, category):
    """Register all common routes for a category.

    Call this from each route module's register_routes(app) function.
    """
    cfg = _get_config(category)

    _register_favourite_route(app, cfg)
    _register_upload_route(app, cfg)
    _register_replace_cover_route(app, cfg)
    _register_save_defaults_route(app, cfg)
    _register_clone_route(app, cfg)
    _register_save_json_route(app, cfg)
    _register_get_missing_route(app, cfg)
    _register_clear_covers_route(app, cfg)
