"""
Web routes for Ansible management interface
"""
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
from app.services.ansible_service import AnsibleService
from app.models import Device, AnsibleExecution, PlaybookExecution, ExecutionFailureReport
from config.database_config import get_db
import logging

# Create blueprint
ansible_web_bp = Blueprint('ansible_web', __name__, url_prefix='/ansible')

# Initialize service
ansible_service = AnsibleService()

@ansible_web_bp.route('/')
def index():
    """Redirect ansible root to playbooks"""
    return redirect(url_for('ansible_web.playbooks'))

@ansible_web_bp.route('/devices')
def devices():
    """Ansible inventory management interface"""
    try:
        # Load current inventory from file
        inventory_data = ansible_service.get_inventory_data()

        # Load all DB devices so the user can see which haven't been synced yet
        with get_db().get_session() as session:
            db_devices = session.query(Device).all()
            db_devices_list = [
                {
                    'hostname': d.hostname,
                    'device_ip': d.device_ip,
                    'status': d.status,
                    'device_type': d.device_type,
                    'location': d.location
                }
                for d in db_devices
            ]

        # Collect all hostnames already present in inventory
        all_inv_hosts = set()
        for group_data in inventory_data.get('groups', {}).values():
            for h in group_data.get('hosts', []):
                all_inv_hosts.add(h['hostname'])

        # Mark which DB devices are in inventory
        for d in db_devices_list:
            d['in_inventory'] = d['hostname'] in all_inv_hosts

        return render_template(
            'ansible/devices.html',
            inventory=inventory_data,
            db_devices=db_devices_list,
            all_inv_hosts=all_inv_hosts
        )
    except Exception as e:
        logging.error(f"Error loading inventory page: {e}")
        flash(f'Error loading inventory: {e}', 'error')
        return render_template(
            'ansible/devices.html',
            inventory={'groups': {}, 'raw_yaml': ''},
            db_devices=[],
            all_inv_hosts=set()
        )

@ansible_web_bp.route('/playbooks')
def playbooks():
    """Playbook management interface"""
    try:
        # Get available playbooks
        playbook_dir = ansible_service.playbook_dir
        playbooks = []
        
        if playbook_dir.exists():
            for file in playbook_dir.glob('*.yml'):
                playbooks.append({
                    'name': file.stem,
                    'filename': file.name,
                    'path': str(file)
                })
        
        # Add built-in playbooks
        builtin_playbooks = [
            {
                'name': 'update_devices',
                'description': 'Update all packages on monitoring devices',
                'builtin': True
            },
            {
                'name': 'restart_service',
                'description': 'Restart monitoring services on devices',
                'builtin': True
            },
            {
                'name': 'distribute_ssh_keys',
                'description': 'Push server public key to devices using password auth',
                'builtin': True
            },
        ]
        
        return render_template('ansible/playbooks.html',
                             playbooks=playbooks,
                             builtin_playbooks=builtin_playbooks)
    except Exception as e:
        logging.error(f"Error loading playbooks: {e}")
        flash(f'Error loading playbooks: {e}', 'error')
        return render_template('ansible/playbooks.html', playbooks=[], builtin_playbooks=[])

@ansible_web_bp.route('/execute', methods=['GET', 'POST'])
def execute():
    """Execute playbook interface"""
    if request.method == 'GET':
        try:
            preselect = request.args.get('playbook', '')
            inventory_data = ansible_service.get_inventory_data()

            # Flatten all unique hosts from inventory for the host picker
            seen = set()
            all_inv_hosts = []
            for group in inventory_data.get('groups', {}).values():
                for h in group.get('hosts', []):
                    if h['hostname'] not in seen:
                        all_inv_hosts.append({
                            'hostname': h['hostname'],
                            'ip': h.get('ansible_host', '')
                        })
                        seen.add(h['hostname'])

            settings = ansible_service.load_settings()

            # Discover custom playbooks (exclude built-ins that have dedicated buttons)
            _builtin_names = {'update_devices', 'restart_service', 'distribute_ssh_keys', 'system_health'}
            custom_playbooks = []
            if ansible_service.playbook_dir.exists():
                for _f in sorted(ansible_service.playbook_dir.glob('*.yml')):
                    if _f.stem.lower() not in _builtin_names:
                        custom_playbooks.append({'name': _f.stem, 'filename': _f.name})

            return render_template('ansible/execute.html',
                                   inventory=inventory_data,
                                   all_inv_hosts=all_inv_hosts,
                                   preselect_playbook=preselect,
                                   custom_playbooks=custom_playbooks,
                                   use_password_auth=settings.get('use_password_auth', False))
        except Exception as e:
            logging.error(f"Error loading execute form: {e}")
            flash(f'Error loading form: {e}', 'error')
            return render_template('ansible/execute.html',
                                   inventory={'groups': {}},
                                   all_inv_hosts=[],
                                   preselect_playbook='',
                                   custom_playbooks=[],
                                   use_password_auth=False)
    
    elif request.method == 'POST':
        # Execute playbook
        try:
            import json as _json
            is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'

            playbook_name = request.form.get('playbook')
            selected_hosts = request.form.getlist('hosts')
            priority = int(request.form.get('priority', 5))
            max_retries = int(request.form.get('max_retries', 0))
            check_mode = bool(request.form.get('check_mode'))
            extra_vars = {}
            
            # Parse extra variables if provided
            extra_vars_str = request.form.get('extra_vars', '').strip()
            if extra_vars_str:
                try:
                    extra_vars = _json.loads(extra_vars_str)
                except _json.JSONDecodeError:
                    if is_ajax:
                        return jsonify({'success': False, 'error': 'Invalid JSON in extra variables'}), 400
                    flash('Invalid JSON format for extra variables', 'error')
                    return redirect(url_for('ansible_web.execute'))
            
            # Add check mode to extra vars if enabled
            if check_mode:
                extra_vars['check_mode'] = True
            
            if not playbook_name:
                if is_ajax:
                    return jsonify({'success': False, 'error': 'Playbook selection is required'}), 400
                flash('Playbook selection is required', 'error')
                return redirect(url_for('ansible_web.execute'))
            
            if not selected_hosts:
                if is_ajax:
                    return jsonify({'success': False, 'error': 'At least one device must be selected'}), 400
                flash('At least one device must be selected', 'error')
                return redirect(url_for('ansible_web.execute'))
            
            # Create builtin playbooks if needed
            if playbook_name == 'update_devices':
                ansible_service.create_update_playbook()
            elif playbook_name == 'restart_service':
                ansible_service.create_restart_service_playbook()
            elif playbook_name == 'system_health':
                ansible_service.create_system_health_playbook()
            elif playbook_name == 'distribute_ssh_keys':
                ansible_service.create_distribute_ssh_keys_playbook()

            # Add controller IP for callbacks
            extra_vars['ansible_controller_ip'] = request.host

            # Force password auth for key distribution, or honour the form toggle
            force_password = (playbook_name == 'distribute_ssh_keys') or \
                             bool(request.form.get('force_password_auth'))

            # Use async execution (returns immediately with execution_id)
            result = ansible_service.execute_playbook_async(
                playbook_name=playbook_name,
                limit_hosts=selected_hosts,
                extra_vars=extra_vars,
                priority=priority,
                max_retries=max_retries,
                force_password_auth=force_password,
            )
            
            if result['success']:
                if is_ajax:
                    return jsonify({'success': True, 'execution_id': result['execution_id']})
                flash(f'Playbook "{playbook_name}" started! Monitoring execution...', 'success')
                return redirect(url_for('ansible_web.execution_details',
                                        execution_id=result['execution_id']))
            else:
                error_msg = result.get('error', 'Unknown error')
                if is_ajax:
                    return jsonify({'success': False, 'error': error_msg}), 500
                flash(f'Playbook execution failed: {error_msg}', 'error')
                return redirect(url_for('ansible_web.execute'))
                
        except Exception as e:
            logging.error(f"Error executing playbook: {e}")
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return jsonify({'success': False, 'error': str(e)}), 500
            flash(f'Error executing playbook: {e}', 'error')
            return redirect(url_for('ansible_web.execute'))

@ansible_web_bp.route('/executions')
def executions():
    """Execution history interface"""
    try:
        executions = ansible_service.get_execution_history(limit=100)
        return render_template('ansible/executions.html', executions=executions)
    except Exception as e:
        logging.error(f"Error loading executions: {e}")
        flash(f'Error loading executions: {e}', 'error')
        return render_template('ansible/executions.html', executions=[])

@ansible_web_bp.route('/executions/<execution_id>')
def execution_details(execution_id):
    """View detailed execution results"""
    try:
        with get_db().get_session() as session:
            execution = session.query(PlaybookExecution).filter_by(
                execution_id=execution_id
            ).first()
            
            if not execution:
                flash('Execution not found', 'error')
                return redirect(url_for('ansible_web.executions'))
            
            # Read log file if available
            log_content = None
            if execution.ansible_log_file:
                try:
                    with open(execution.ansible_log_file, 'r') as f:
                        log_content = f.read()
                except FileNotFoundError:
                    log_content = "Log file not found"
            
            return render_template('ansible/execution_details.html', 
                                 execution=execution, 
                                 log_content=log_content)
    except Exception as e:
        logging.error(f"Error loading execution details: {e}")
        flash(f'Error loading execution details: {e}', 'error')
        return redirect(url_for('ansible_web.executions'))

@ansible_web_bp.route('/executions/<execution_id>/live-popup')
def execution_live_popup(execution_id):
    """Standalone popup window for live execution output"""
    return render_template('ansible/live_popup.html', execution_id=execution_id)

@ansible_web_bp.route('/ssh/setup')
def ssh_setup():
    """SSH key setup interface"""
    try:
        # Check if SSH key exists
        key_exists = ansible_service.ssh_key_path.exists()
        public_key = None
        
        if key_exists:
            public_key_path = ansible_service.ssh_key_path.with_suffix('.pub')
            if public_key_path.exists():
                with open(public_key_path, 'r') as f:
                    public_key = f.read().strip()

        settings = ansible_service.load_settings()
        
        return render_template('ansible/ssh_setup.html', 
                             key_exists=key_exists,
                             public_key=public_key,
                             settings=settings)
    except Exception as e:
        logging.error(f"Error in SSH setup page: {e}")
        flash(f'Error loading SSH setup: {e}', 'error')
        return render_template('ansible/ssh_setup.html', key_exists=False, public_key=None, settings={})


@ansible_web_bp.route('/ssh/settings', methods=['POST'])
def save_ssh_settings():
    """Save SSH settings (fallback password etc.)"""
    try:
        fallback_password = request.form.get('ssh_fallback_password', '').strip()
        if not fallback_password:
            flash('Password cannot be empty.', 'error')
            return redirect(url_for('ansible_web.ssh_setup'))

        use_password_auth = request.form.get('use_password_auth') == 'on'
        ansible_service.save_settings({
            'ssh_fallback_password': fallback_password,
            'use_password_auth': use_password_auth,
        })
        flash('SSH settings saved successfully.', 'success')
    except Exception as e:
        logging.error(f"Error saving SSH settings: {e}")
        flash(f'Error saving SSH settings: {e}', 'error')
    return redirect(url_for('ansible_web.ssh_setup'))

@ansible_web_bp.route('/ssh/generate', methods=['POST'])
def generate_ssh_keys():
    """Generate new SSH keys"""
    try:
        result = ansible_service.setup_ssh_keys()
        
        if result['success']:
            flash('SSH keys generated successfully!', 'success')
        else:
            flash(f'Error generating SSH keys: {result.get("error")}', 'error')
        
        return redirect(url_for('ansible_web.ssh_setup'))
    except Exception as e:
        logging.error(f"Error generating SSH keys: {e}")
        flash(f'Error generating SSH keys: {e}', 'error')
        return redirect(url_for('ansible_web.ssh_setup'))

@ansible_web_bp.route('/ssh/test', methods=['POST'])
def test_ssh():
    """Test SSH connectivity to selected devices"""
    try:
        selected_ips = request.form.getlist('device_ips')
        
        if not selected_ips:
            flash('Please select at least one device to test', 'error')
            return redirect(url_for('ansible_web.devices'))
        
        # Test connectivity
        results = ansible_service.bulk_ssh_test(selected_ips)
        
        # Count results
        successful = sum(1 for r in results.values() if r.get('success'))
        total = len(results)
        
        flash(f'SSH test completed: {successful}/{total} devices reachable', 
              'success' if successful == total else 'warning')
        
        return render_template('ansible/ssh_test_results.html', results=results)
        
    except Exception as e:
        logging.error(f"Error testing SSH: {e}")
        flash(f'Error testing SSH: {e}', 'error')
        return redirect(url_for('ansible_web.devices'))

# API endpoints for AJAX calls
@ansible_web_bp.route('/api/refresh_inventory', methods=['POST'])
def api_refresh_inventory():
    """AJAX endpoint to refresh inventory"""
    try:
        inventory = ansible_service.generate_dynamic_inventory()
        device_count = len(inventory.get('all', {}).get('children', {}).get('monitoring_devices', {}).get('hosts', {}))
        
        return jsonify({
            'success': True,
            'message': f'Inventory refreshed with {device_count} devices'
        })
    except Exception as e:
        logging.error(f"Error refreshing inventory: {e}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

@ansible_web_bp.route('/api/execution_status/<execution_id>')
def api_execution_status(execution_id):
    """AJAX endpoint to get execution status"""
    try:
        with get_db().get_session() as session:
            execution = session.query(PlaybookExecution).filter_by(
                execution_id=execution_id
            ).first()
            
            if not execution:
                return jsonify({'error': 'Execution not found'}), 404
            
            return jsonify({
                'id': execution.id,
                'status': execution.status,
                'start_time': execution.start_time.isoformat() if execution.start_time else None,
                'end_time': execution.end_time.isoformat() if execution.end_time else None,
                'exit_code': execution.exit_code,
                'successful_hosts': execution.successful_hosts,
                'failed_hosts': execution.failed_hosts,
                'unreachable_hosts': execution.unreachable_hosts
            })
    except Exception as e:
        logging.error(f"Error getting execution status: {e}")
        return jsonify({'error': str(e)}), 500

@ansible_web_bp.route('/upload_playbook', methods=['POST'])
def upload_playbook():
    """Upload a custom playbook file"""
    try:
        if 'playbook_file' not in request.files:
            flash('No playbook file selected', 'error')
            return redirect(url_for('ansible_web.playbooks'))
        
        file = request.files['playbook_file']
        if file.filename == '':
            flash('No playbook file selected', 'error')
            return redirect(url_for('ansible_web.playbooks'))
        
        if file and file.filename.lower().endswith(('.yml', '.yaml')):
            # Get playbook name
            playbook_name = request.form.get('playbook_name', '').strip()
            if not playbook_name:
                playbook_name = file.filename.rsplit('.', 1)[0]
            
            # Clean filename
            import re
            safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', playbook_name)
            filename = f"{safe_filename}.yml"
            
            # Save file
            playbook_path = ansible_service.playbook_dir / filename
            file.save(str(playbook_path))
            
            flash(f'Playbook "{filename}" uploaded successfully!', 'success')
        else:
            flash('Invalid file type. Please upload a .yml or .yaml file.', 'error')
        
        return redirect(url_for('ansible_web.playbooks'))
        
    except Exception as e:
        logging.error(f"Error uploading playbook: {e}")
        flash(f'Error uploading playbook: {e}', 'error')
        return redirect(url_for('ansible_web.playbooks'))

@ansible_web_bp.route('/playbook/content')
def playbook_content():
    """Get playbook content for viewing"""
    try:
        playbook_path = request.args.get('path')
        if not playbook_path:
            return "No playbook path provided", 400
        
        # Security check - ensure path is within playbooks directory
        from pathlib import Path
        requested_path = Path(playbook_path)
        if not requested_path.is_absolute():
            requested_path = Path.cwd() / requested_path
        
        # Ensure path is within playbook directory
        try:
            requested_path.resolve().relative_to(ansible_service.playbook_dir.resolve())
        except ValueError:
            return "Invalid playbook path", 400
        
        if not requested_path.exists():
            return "Playbook file not found", 404
        
        with open(requested_path, 'r', encoding='utf-8') as f:
            content = f.read()
        
        return content, 200, {'Content-Type': 'text/plain'}
        
    except Exception as e:
        logging.error(f"Error reading playbook content: {e}")
        return f"Error reading playbook: {e}", 500

@ansible_web_bp.route('/playbook/save', methods=['POST'])
def save_playbook():
    """Save a new or existing playbook"""
    try:
        data = request.get_json()
        if not data:
            return jsonify({'error': 'No data provided'}), 400
        
        name = data.get('name', '').strip()
        content = data.get('content', '').strip()
        is_new = data.get('is_new', False)
        
        if not name:
            return jsonify({'error': 'Playbook name is required'}), 400
        
        if not content:
            return jsonify({'error': 'Playbook content is required'}), 400
        
        # Validate YAML syntax
        try:
            import yaml
            yaml.safe_load(content)
        except yaml.YAMLError as e:
            return jsonify({'error': f'Invalid YAML syntax: {e}'}), 400
        
        # Clean filename
        import re
        safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', name)
        filename = f"{safe_filename}.yml"
        
        # Save file
        playbook_path = ansible_service.playbook_dir / filename
        
        # Check if file exists and not updating
        if is_new and playbook_path.exists():
            return jsonify({'error': f'Playbook {filename} already exists'}), 400
        
        with open(playbook_path, 'w', encoding='utf-8') as f:
            f.write(content)
        
        return jsonify({
            'success': True,
            'message': f'Playbook {filename} saved successfully',
            'filename': filename
        })
        
    except Exception as e:
        logging.error(f"Error saving playbook: {e}")
        return jsonify({'error': str(e)}), 500

@ansible_web_bp.route('/playbook/validate', methods=['POST'])
def validate_playbook():
    """Validate playbook YAML syntax and structure"""
    try:
        data = request.get_json()
        if not data:
            return jsonify({'error': 'No data provided'}), 400
        
        content = data.get('content', '').strip()
        if not content:
            return jsonify({'error': 'No content to validate'}), 400
        
        # Validate YAML syntax
        try:
            import yaml
            parsed = yaml.safe_load(content)
        except yaml.YAMLError as e:
            return jsonify({'valid': False, 'error': f'YAML syntax error: {e}'})
        
        # Basic Ansible playbook structure validation
        if not isinstance(parsed, list):
            return jsonify({'valid': False, 'error': 'Playbook must be a list of plays'})
        
        for i, play in enumerate(parsed):
            if not isinstance(play, dict):
                return jsonify({'valid': False, 'error': f'Play {i+1} must be a dictionary'})
            
            if 'hosts' not in play:
                return jsonify({'valid': False, 'error': f'Play {i+1} is missing required "hosts" field'})
            
            if 'tasks' not in play and 'roles' not in play:
                return jsonify({'valid': False, 'error': f'Play {i+1} must have either "tasks" or "roles"'})
        
        return jsonify({'valid': True, 'message': 'Playbook is valid'})
        
    except Exception as e:
        logging.error(f"Error validating playbook: {e}")
        return jsonify({'valid': False, 'error': str(e)})

@ansible_web_bp.route('/playbook/delete', methods=['POST'])
def delete_playbook():
    """Delete a custom playbook"""
    try:
        data = request.get_json()
        if not data:
            return jsonify({'error': 'No data provided'}), 400
        
        playbook_name = data.get('playbook_name', '').strip()
        if not playbook_name:
            return jsonify({'error': 'Playbook name is required'}), 400
        
        # Find the playbook file
        import re
        safe_filename = re.sub(r'[^a-zA-Z0-9_-]', '_', playbook_name)
        
        # Try both with and without .yml extension
        possible_files = [
            ansible_service.playbook_dir / f"{safe_filename}.yml",
            ansible_service.playbook_dir / f"{safe_filename}.yaml",
            ansible_service.playbook_dir / f"{playbook_name}.yml",
            ansible_service.playbook_dir / f"{playbook_name}.yaml"
        ]
        
        playbook_path = None
        for path in possible_files:
            if path.exists():
                playbook_path = path
                break
        
        if not playbook_path:
            return jsonify({'error': f'Playbook {playbook_name} not found'}), 404
        
        # Security check - ensure path is within playbooks directory
        try:
            playbook_path.resolve().relative_to(ansible_service.playbook_dir.resolve())
        except ValueError:
            return jsonify({'error': 'Invalid playbook path'}), 400
        
        # Delete the file
        playbook_path.unlink()
        
        return jsonify({
            'success': True,
            'message': f'Playbook {playbook_name} deleted successfully'
        })
        
    except Exception as e:
        logging.error(f"Error deleting playbook: {e}")
        return jsonify({'error': str(e)}), 500


@ansible_web_bp.route('/failure-reports')
def failure_reports():
    """View all saved execution failure reports."""
    try:
        with get_db().get_session() as session:
            reports = session.query(ExecutionFailureReport)\
                .order_by(ExecutionFailureReport.saved_at.desc())\
                .all()
            reports_data = [r.to_dict() for r in reports]
        return render_template('ansible/failure_reports.html', reports=reports_data)
    except Exception as e:
        logging.error(f"Error loading failure reports: {e}")
        flash(f'Error loading failure reports: {e}', 'error')
        return render_template('ansible/failure_reports.html', reports=[])