"""
Ansible and SSH management API endpoints
"""
from flask import Blueprint, request, jsonify
import json
import os
from datetime import datetime
from app.services.ansible_service import AnsibleService
from app.models import Device, AnsibleExecution, ExecutionFailureReport
from config.database_config import get_db
import logging

# Create blueprint
ansible_bp = Blueprint('ansible', __name__, url_prefix='/api/ansible')

# Initialize service
ansible_service = AnsibleService()

@ansible_bp.route('/inventory', methods=['GET'])
def get_inventory():
    """Get current Ansible inventory (structured)"""
    try:
        data = ansible_service.get_inventory_data()
        return jsonify({'success': True, 'inventory': data})
    except Exception as e:
        logging.error(f"Error getting inventory: {e}")
        return jsonify({'error': str(e), 'success': False}), 500

@ansible_bp.route('/inventory/raw', methods=['GET'])
def get_inventory_raw():
    """Get raw YAML inventory text"""
    try:
        data = ansible_service.get_inventory_data()
        return jsonify({'success': True, 'yaml': data.get('raw_yaml', '')})
    except Exception as e:
        return jsonify({'error': str(e), 'success': False}), 500

@ansible_bp.route('/inventory/sync', methods=['POST'])
def sync_inventory():
    """Sync all active app devices into monitoring_devices and wmt_clients inventory groups"""
    try:
        result = ansible_service.sync_devices_to_inventory()
        ansible_service.sync_wmt_devices_to_inventory()
        status = 200 if result.get('success') else 400
        return jsonify(result), status
    except Exception as e:
        logging.error(f"Error syncing inventory: {e}")
        return jsonify({'error': str(e), 'success': False}), 500

@ansible_bp.route('/inventory/group/add', methods=['POST'])
def add_inventory_group():
    """Add a new inventory group"""
    try:
        data = request.get_json()
        group_name = (data or {}).get('group_name', '').strip()
        if not group_name:
            return jsonify({'success': False, 'error': 'group_name is required'}), 400
        result = ansible_service.add_group_to_inventory(group_name)
        return jsonify(result), 200 if result.get('success') else 400
    except Exception as e:
        return jsonify({'error': str(e), 'success': False}), 500

@ansible_bp.route('/inventory/group/remove', methods=['POST'])
def remove_inventory_group():
    """Remove an inventory group"""
    try:
        data = request.get_json()
        group_name = (data or {}).get('group_name', '').strip()
        if not group_name:
            return jsonify({'success': False, 'error': 'group_name is required'}), 400
        result = ansible_service.remove_group_from_inventory(group_name)
        return jsonify(result), 200 if result.get('success') else 400
    except Exception as e:
        return jsonify({'error': str(e), 'success': False}), 500

@ansible_bp.route('/inventory/host/add', methods=['POST'])
def add_inventory_host():
    """Add a host to an inventory group"""
    try:
        data = request.get_json()
        if not data:
            return jsonify({'success': False, 'error': 'JSON body required'}), 400
        group = data.get('group', '').strip()
        hostname = data.get('hostname', '').strip()
        ip = data.get('ip', '').strip()
        ssh_user = data.get('ssh_user', 'pi').strip() or 'pi'
        ssh_port = int(data.get('ssh_port', 22))
        use_key = bool(data.get('use_key', True))
        password = data.get('password', None)
        if not group or not hostname or not ip:
            return jsonify({'success': False, 'error': 'group, hostname and ip are required'}), 400
        result = ansible_service.add_host_to_inventory(
            group=group, hostname=hostname, ip=ip,
            ssh_user=ssh_user, ssh_port=ssh_port,
            use_key=use_key, password=password
        )
        return jsonify(result), 200 if result.get('success') else 400
    except Exception as e:
        return jsonify({'error': str(e), 'success': False}), 500

@ansible_bp.route('/inventory/host/remove', methods=['POST'])
def remove_inventory_host():
    """Remove a host from an inventory group"""
    try:
        data = request.get_json()
        group = (data or {}).get('group', '').strip()
        hostname = (data or {}).get('hostname', '').strip()
        if not group or not hostname:
            return jsonify({'success': False, 'error': 'group and hostname are required'}), 400
        result = ansible_service.remove_host_from_inventory(group=group, hostname=hostname)
        return jsonify(result), 200 if result.get('success') else 400
    except Exception as e:
        return jsonify({'error': str(e), 'success': False}), 500

@ansible_bp.route('/inventory/refresh', methods=['POST'])
def refresh_inventory():
    """Refresh Ansible inventory from database (legacy alias for /sync)"""
    try:
        result = ansible_service.sync_devices_to_inventory()
        ansible_service.sync_wmt_devices_to_inventory()
        return jsonify(result), 200 if result.get('success') else 400
    except Exception as e:
        logging.error(f"Error refreshing inventory: {e}")
        return jsonify({'error': str(e), 'success': False}), 500

@ansible_bp.route('/playbooks', methods=['GET'])
def list_playbooks():
    """List available Ansible playbooks"""
    try:
        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),
                    'modified': datetime.fromtimestamp(file.stat().st_mtime).isoformat()
                })
        
        # 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 jsonify({
            'success': True,
            'playbooks': playbooks,
            'builtin_playbooks': builtin_playbooks
        })
    except Exception as e:
        logging.error(f"Error listing playbooks: {e}")
        return jsonify({
            'error': str(e),
            'success': False
        }), 500

@ansible_bp.route('/execute', methods=['POST'])
def execute_playbook():
    """
    Execute Ansible playbook
    
    Expected JSON:
    {
        "playbook": "update_devices",
        "limit_hosts": ["device-01", "device-02"],  # optional
        "extra_vars": {"key": "value"},  # optional
        "create_builtin": True  # optional, create builtin playbook if needed
    }
    """
    try:
        data = request.get_json()
        
        if not data or not data.get('playbook'):
            return jsonify({
                'error': 'Playbook name is required',
                'success': False
            }), 400
        
        playbook_name = data['playbook']
        limit_hosts = data.get('limit_hosts')
        extra_vars = data.get('extra_vars', {})
        create_builtin = data.get('create_builtin', True)
        
        # Create builtin playbooks if they don't exist
        if create_builtin:
            if playbook_name == 'update_devices':
                ansible_service.create_update_playbook()
            elif playbook_name == 'restart_service':
                ansible_service.create_restart_service_playbook()
        
        # Add controller IP to extra vars for callbacks
        extra_vars['ansible_controller_ip'] = request.host
        
        # Execute playbook
        result = ansible_service.execute_playbook(
            playbook_name=playbook_name,
            limit_hosts=limit_hosts,
            extra_vars=extra_vars
        )
        
        if result['success']:
            return jsonify({
                'success': True,
                'message': 'Playbook execution started',
                'execution_id': result['execution_id'],
                'log_file': result['log_file']
            })
        else:
            return jsonify(result), 500
            
    except Exception as e:
        logging.error(f"Error executing playbook: {e}")
        return jsonify({
            'error': str(e),
            'success': False
        }), 500

@ansible_bp.route('/executions', methods=['GET'])
def get_executions():
    """Get Ansible execution history"""
    try:
        limit = min(int(request.args.get('limit', 50)), 200)
        executions = ansible_service.get_execution_history(limit=limit)
        
        return jsonify({
            'success': True,
            'executions': executions
        })
    except Exception as e:
        logging.error(f"Error getting executions: {e}")
        return jsonify({
            'error': str(e),
            'success': False
        }), 500


@ansible_bp.route('/executions/<execution_id>/live', methods=['GET'])
def get_execution_live(execution_id):
    """Poll current log + status for a running or finished execution (UUID)."""
    result = ansible_service.get_live_execution(execution_id)
    if not result.get('success'):
        return jsonify(result), 404
    return jsonify(result), 200

@ansible_bp.route('/executions/<int:execution_id>', methods=['GET'])
def get_execution_details(execution_id):
    """Get detailed execution information"""
    try:
        with get_db().get_session() as session:
            execution = session.query(AnsibleExecution).get(execution_id)
            
            if not execution:
                return jsonify({
                    'error': 'Execution not found',
                    'success': False
                }), 404
            
            execution_data = {
                'id': execution.id,
                'playbook_name': execution.playbook_name,
                'target_devices': json.loads(execution.target_devices) if execution.target_devices else [],
                'command_line': execution.command_line,
                'start_time': execution.start_time.isoformat() if execution.start_time else None,
                'end_time': execution.end_time.isoformat() if execution.end_time else None,
                'status': execution.status,
                'exit_code': execution.exit_code,
                'stdout_log': execution.stdout_log,
                'stderr_log': execution.stderr_log,
                'successful_hosts': execution.successful_hosts,
                'failed_hosts': execution.failed_hosts,
                'unreachable_hosts': execution.unreachable_hosts
            }
            
            # Read log file if it exists
            if execution.ansible_log_file and os.path.exists(execution.ansible_log_file):
                with open(execution.ansible_log_file, 'r') as f:
                    execution_data['full_log'] = f.read()
            
            return jsonify({
                'success': True,
                'execution': execution_data
            })
            
    except Exception as e:
        logging.error(f"Error getting execution details: {e}")
        return jsonify({
            'error': str(e),
            'success': False
        }), 500

@ansible_bp.route('/ssh/test', methods=['POST'])
def test_ssh_connectivity():
    """
    Test SSH connectivity to devices
    
    Expected JSON:
    {
        "device_ips": ["192.168.1.100", "192.168.1.101"],
        "username": "pi"  # optional, defaults to "pi"
    }
    """
    try:
        data = request.get_json()
        
        if not data or not data.get('device_ips'):
            return jsonify({
                'error': 'device_ips list is required',
                'success': False
            }), 400
        
        device_ips = data['device_ips']
        username = data.get('username', 'pi')
        
        # Test connectivity
        if len(device_ips) == 1:
            # Single device test
            result = ansible_service.test_ssh_connectivity(device_ips[0], username)
            return jsonify({
                'success': True,
                'results': {device_ips[0]: result}
            })
        else:
            # Bulk test
            results = ansible_service.bulk_ssh_test(device_ips)
            
            # Summary
            successful = sum(1 for r in results.values() if r.get('success'))
            total = len(results)
            
            return jsonify({
                'success': True,
                'results': results,
                'summary': {
                    'successful': successful,
                    'failed': total - successful,
                    'total': total
                }
            })
            
    except Exception as e:
        logging.error(f"Error testing SSH connectivity: {e}")
        return jsonify({
            'error': str(e),
            'success': False
        }), 500


@ansible_bp.route('/ssh/test-password', methods=['POST'])
def test_password_auth():
    """
    Test password-only SSH authentication to a single device.
    Use this to verify the configured password is correct before deploying keys.

    Expected JSON:
    {
        "device_ip": "10.76.157.145",
        "password": "raspberry",       # optional — uses saved setting if omitted
        "username": "pi",              # optional
        "port": 22                     # optional
    }
    """
    try:
        data = request.get_json() or {}
        device_ip = (data.get('device_ip') or '').strip()
        if not device_ip:
            return jsonify({'success': False, 'error': 'device_ip is required'}), 400

        # Use provided password, fall back to saved setting
        password = data.get('password') or ansible_service.load_settings().get('ssh_fallback_password', '')
        if not password:
            return jsonify({'success': False,
                            'error': 'No password provided and none saved in SSH Settings'}), 400

        username = data.get('username', 'pi')
        port = int(data.get('port', 22))

        result = ansible_service.test_password_auth(device_ip, password, username, port)
        status = 200 if result.get('success') else 400
        return jsonify(result), status

    except Exception as e:
        logging.error(f"Error testing password auth: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500


@ansible_bp.route('/ssh/distribute-keys', methods=['POST'])
def distribute_ssh_keys():
    """
    Push the server's public SSH key to all (or selected) devices using password auth.
    After this completes, all other playbooks can use key-based authentication.

    Optional JSON body:
    {
        "limit_hosts": ["RPI-ABC1", "RPI-ABC2"]   # omit to target all devices
    }
    """
    try:
        settings = ansible_service.load_settings()
        if not settings.get('ssh_fallback_password'):
            return jsonify({
                'success': False,
                'error': 'No SSH password configured. Set it in SSH Settings before distributing keys.'
            }), 400

        # Make sure the public key exists
        public_key_path = ansible_service.ssh_key_path.with_suffix('.pub')
        if not public_key_path.exists():
            return jsonify({
                'success': False,
                'error': 'Public key not found at ansible/ssh_keys/app_key.pub. Generate SSH keys first.'
            }), 400

        data = request.get_json() or {}
        limit_hosts = data.get('limit_hosts') or None

        ansible_service.create_distribute_ssh_keys_playbook()
        result = ansible_service.execute_playbook_async(
            playbook_name='distribute_ssh_keys',
            limit_hosts=limit_hosts,
            force_password_auth=True,
        )
        return jsonify(result), 200 if result.get('success') else 500

    except Exception as e:
        logging.error(f"Error distributing SSH keys: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500


@ansible_bp.route('/ssh/keys/setup', methods=['POST'])
def setup_ssh_keys():
    """Setup SSH keys for Ansible authentication"""
    try:
        result = ansible_service.setup_ssh_keys()
        return jsonify(result)
    except Exception as e:
        logging.error(f"Error setting up SSH keys: {e}")
        return jsonify({
            'error': str(e),
            'success': False
        }), 500

@ansible_bp.route('/ssh/keys/public', methods=['GET'])
def get_public_key():
    """Get public key for distribution to devices"""
    try:
        public_key_path = ansible_service.ssh_key_path.with_suffix('.pub')
        
        if not public_key_path.exists():
            return jsonify({
                'error': 'Public key not found. Run SSH setup first.',
                'success': False
            }), 404
        
        with open(public_key_path, 'r') as f:
            public_key = f.read().strip()
        
        return jsonify({
            'success': True,
            'public_key': public_key,
            'key_path': str(public_key_path)
        })
    except Exception as e:
        logging.error(f"Error getting public key: {e}")
        return jsonify({
            'error': str(e),
            'success': False
        }), 500

@ansible_bp.route('/devices/status', methods=['GET'])
def get_devices_status():
    """Get status of all devices for Ansible operations"""
    try:
        with get_db().get_session() as session:
            devices = session.query(Device).all()
            
            device_data = []
            for device in devices:
                device_data.append({
                    'id': device.id,
                    'hostname': device.hostname,
                    'device_ip': device.device_ip,
                    'nume_masa': device.nume_masa,
                    'status': device.status,
                    'last_seen': device.last_seen.isoformat() if device.last_seen else None,
                    'device_type': device.device_type,
                    'os_version': device.os_version,
                    'location': device.location
                })
            
            return jsonify({
                'success': True,
                'devices': device_data,
                'total_count': len(device_data)
            })
            
    except Exception as e:
        logging.error(f"Error getting devices status: {e}")
        return jsonify({
            'error': str(e),
            'success': False
        }), 500

# Callback endpoints for Ansible playbooks
@ansible_bp.route('/callback/update_complete', methods=['POST'])
def update_complete_callback():
    """Callback endpoint for update completion"""
    try:
        data = request.get_json()
        logging.info(f"Update completed for {data.get('hostname')}: {data}")
        
        # You could update device status, send notifications, etc.
        return jsonify({'success': True})
    except Exception as e:
        logging.error(f"Error in update callback: {e}")
        return jsonify({'error': str(e)}), 500

@ansible_bp.route('/callback/service_restarted', methods=['POST'])
def service_restart_callback():
    """Callback endpoint for service restart completion"""
    try:
        data = request.get_json()
        logging.info(f"Service restarted for {data.get('hostname')}: {data}")
        
        return jsonify({'success': True})
    except Exception as e:
        logging.error(f"Error in service restart callback: {e}")
        return jsonify({'error': str(e)}), 500


# ── Failure Reports ────────────────────────────────────────────────────── #

@ansible_bp.route('/failure-reports', methods=['GET'])
def list_failure_reports():
    """Return all saved execution failure reports, newest first."""
    try:
        with get_db().get_session() as session:
            reports = session.query(ExecutionFailureReport)\
                .order_by(ExecutionFailureReport.saved_at.desc())\
                .all()
            return jsonify({'success': True, 'reports': [r.to_dict() for r in reports]})
    except Exception as e:
        logging.error(f"Error listing failure reports: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500


@ansible_bp.route('/failure-reports', methods=['POST'])
def save_failure_report():
    """
    Save a failure report for an execution.
    Parses the PLAY RECAP from the execution log to extract per-host failure reasons.

    Expected JSON:
    {
        "execution_id": "uuid",
        "note": "optional free-text note"   # optional
    }
    """
    import re
    try:
        data = request.get_json() or {}
        execution_id = (data.get('execution_id') or '').strip()
        if not execution_id:
            return jsonify({'success': False, 'error': 'execution_id is required'}), 400

        with get_db().get_session() as session:
            from app.models import PlaybookExecution
            execution = session.query(PlaybookExecution).filter_by(
                execution_id=execution_id
            ).first()
            if not execution:
                return jsonify({'success': False, 'error': 'Execution not found'}), 404

            # Parse PLAY RECAP for per-host stats
            log_text = execution.stdout_log or ''
            recap_re = re.compile(
                r'^(\S.*?)\s*:\s*ok=(\d+)\s+changed=(\d+)\s+unreachable=(\d+)\s+failed=(\d+)',
                re.MULTILINE
            )
            failed_hosts = []
            failed_count = 0
            unreachable_count = 0
            for m in recap_re.finditer(log_text):
                hostname    = m.group(1).strip()
                unreachable = int(m.group(4))
                failed      = int(m.group(5))
                if unreachable > 0:
                    failed_hosts.append({'hostname': hostname, 'status': 'unreachable',
                                         'reason': 'Host unreachable via SSH'})
                    unreachable_count += 1
                elif failed > 0:
                    failed_hosts.append({'hostname': hostname, 'status': 'failed',
                                         'reason': f'{failed} task(s) failed'})
                    failed_count += 1

            if not failed_hosts:
                return jsonify({'success': False,
                                'error': 'No failed or unreachable hosts found in this execution'}), 400

            # Avoid duplicate reports for the same execution
            existing = session.query(ExecutionFailureReport).filter_by(
                execution_id=execution_id
            ).first()
            if existing:
                return jsonify({'success': False,
                                'error': 'A report for this execution already exists',
                                'report_id': existing.id}), 409

            report = ExecutionFailureReport(
                execution_id=execution_id,
                playbook_name=execution.playbook_name,
                saved_at=datetime.utcnow(),
                failed_count=failed_count,
                unreachable_count=unreachable_count,
                failed_hosts=json.dumps(failed_hosts),
                note=data.get('note', ''),
            )
            session.add(report)
            session.flush()
            report_id = report.id

        return jsonify({'success': True, 'report_id': report_id,
                        'failed_count': failed_count,
                        'unreachable_count': unreachable_count}), 201

    except Exception as e:
        logging.error(f"Error saving failure report: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500


@ansible_bp.route('/failure-reports/<int:report_id>', methods=['DELETE'])
def delete_failure_report(report_id):
    """Delete a saved failure report."""
    try:
        with get_db().get_session() as session:
            report = session.query(ExecutionFailureReport).get(report_id)
            if not report:
                return jsonify({'success': False, 'error': 'Report not found'}), 404
            session.delete(report)
        return jsonify({'success': True})
    except Exception as e:
        logging.error(f"Error deleting failure report {report_id}: {e}")
        return jsonify({'success': False, 'error': str(e)}), 500