"""
Asset/Inventory Management Router

Endpoints for asset management (Wazuh Agents + manual systems).
"""
from typing import Optional, List, Any
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from pydantic import BaseModel, field_validator

from app.database import get_db
from app.models.user import User
from app.models.asset import Asset, AssetSource, AssetStatus
from app.models.vulnerability import Vulnerability, VulnerabilityStatus
from app.models.audit_log import AuditLog, AuditEventType
from app.auth.dependencies import get_current_user, RequireEditor, RequireAdmin
import logging

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/assets", tags=["Assets"])


# ============================================
# Pydantic Schemas
# ============================================

class AssetResponse(BaseModel):
    id: int
    hostname: str
    ip_address: Optional[str]
    wazuh_agent_id: Optional[str]
    operating_system: Optional[str]
    os_version: Optional[str]
    source: AssetSource
    status: AssetStatus
    last_scan: Optional[datetime]
    last_seen: Optional[datetime]
    vulnerability_count: Optional[int] = 0
    policy_id: Optional[int] = None
    description: Optional[str] = None
    location: Optional[str] = None
    owner: Optional[str] = None
    assigned_user_id: Optional[int] = None
    assigned_user_name: Optional[str] = None
    # assigned_group_id obsolete, replaced by groups list
    groups: List[str] = [] # List of Team Names for display
    policy_name: Optional[str] = None

    @field_validator('groups', mode='before')
    @classmethod
    def map_groups_to_names(cls, v: Any) -> List[str]:
        if not v:
            return []
        # Wenn es bereits Strings sind (z.B. manuell gesetzt)
        if isinstance(v, list) and all(isinstance(x, str) for x in v):
            return v
        # Wenn es SQLAlchemy-Objekte sind
        return [g.name for g in v if hasattr(g, 'name')]

    class Config:
        from_attributes = True


class AssetAssignRequest(BaseModel):
    user_id: Optional[int] = None
    group_id: Optional[int] = None


class AssetCreateRequest(BaseModel):
    hostname: str
    ip_address: Optional[str] = None
    operating_system: Optional[str] = None
    os_version: Optional[str] = None
    description: Optional[str] = None
    location: Optional[str] = None
    owner: Optional[str] = None
    policy_id: Optional[int] = None


class AssetUpdateRequest(BaseModel):
    hostname: Optional[str] = None
    ip_address: Optional[str] = None
    operating_system: Optional[str] = None
    os_version: Optional[str] = None
    status: Optional[AssetStatus] = None
    description: Optional[str] = None
    location: Optional[str] = None
    owner: Optional[str] = None
    policy_id: Optional[int] = None
    # Plan E — URS inputs (criticality multiplier + framework list)
    criticality: Optional[str] = None        # low | normal | high | critical
    compliance_frameworks: Optional[List[str]] = None


class BulkAssetUpdateRequest(BaseModel):
    asset_ids: List[int]
    assigned_user_id: Optional[int] = None
    assigned_group_id: Optional[int] = None
    policy_id: Optional[int] = None


# ============================================
# Endpoints
# ============================================

@router.post("/bulk-update")
async def bulk_update_assets(
    update_data: BulkAssetUpdateRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """Updates multiple assets at once (assignment, policy)"""
    assets = db.query(Asset).filter(Asset.id.in_(update_data.asset_ids)).all()
    
    if not assets:
        raise HTTPException(status_code=404, detail="No assets found")

    update_fields = {}
    if update_data.assigned_user_id is not None:
        update_fields["assigned_user_id"] = update_data.assigned_user_id if update_data.assigned_user_id != -1 else None
    if update_data.assigned_group_id is not None:
        update_fields["assigned_group_id"] = update_data.assigned_group_id if update_data.assigned_group_id != -1 else None
    if update_data.policy_id is not None:
        update_fields["policy_id"] = update_data.policy_id if update_data.policy_id != -1 else None

    if not update_fields:
         raise HTTPException(status_code=400, detail="No update fields specified")

    count = 0
    from app.models.group import Group
    
    for asset in assets:
        for field, value in update_fields.items():
            if field == "assigned_group_id":
                # Special handling for legacy group assignment
                if value is None:
                    asset.groups = []
                else:
                    group = db.query(Group).filter(Group.id == value).first()
                    if group:
                        asset.groups = [group]
            else:
                setattr(asset, field, value)
        count += 1
    
    db.commit()

    # Audit-Log
    audit_log = AuditLog(
        user_id=current_user.id,
        event_type=AuditEventType.ASSET_UPDATED,
        event_description=f"Bulk update performed for {count} assets",
        resource_type="asset",
        resource_id="multiple",
        timestamp=datetime.now()
    )
    db.add(audit_log)
    db.commit()

    return {"message": f"{count} assets successfully updated", "count": count}

@router.get("", response_model=List[AssetResponse])
async def list_assets(
    status: Optional[AssetStatus] = Query(None),
    source: Optional[AssetSource] = Query(None),
    search: Optional[str] = Query(None, description="Suche in Hostname, IP"),
    limit: int = Query(100, le=1000),
    offset: int = Query(0),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Liste aller Assets mit Filterung"""
    query = db.query(Asset)

    if status:
        query = query.filter(Asset.status == status)
    else:
        query = query.filter(Asset.status.in_([AssetStatus.ACTIVE, AssetStatus.INACTIVE]))

    if source:
        query = query.filter(Asset.source == source)

    if search:
        search_pattern = f"%{search}%"
        query = query.filter(
            (Asset.hostname.ilike(search_pattern)) |
            (Asset.ip_address.ilike(search_pattern))
        )

    assets = query.offset(offset).limit(limit).all()

    # Vulnerability-Counts + assigned user info
    results = []
    for asset in assets:
        vuln_count = db.query(Vulnerability).filter(
            Vulnerability.asset_id == asset.id,
            Vulnerability.status == VulnerabilityStatus.open
        ).count()

        asset.vulnerability_count = vuln_count
        asset.assigned_user_name = asset.assigned_user.username if asset.assigned_user else None
        asset.policy_name = asset.policy.name if asset.policy else None
        results.append(asset)

    return results


@router.get("/{asset_id}", response_model=AssetResponse)
async def get_asset_detail(
    asset_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Asset-Details"""
    asset = db.query(Asset).filter(Asset.id == asset_id).first()

    if not asset:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Asset not found"
        )

    vuln_count = db.query(Vulnerability).filter(
        Vulnerability.asset_id == asset_id,
        Vulnerability.status == VulnerabilityStatus.open
    ).count()

    asset.vulnerability_count = vuln_count
    return asset


@router.post("", status_code=status.HTTP_201_CREATED, response_model=AssetResponse)
async def create_asset(
    asset_data: AssetCreateRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """
    Erstellt manuelles Asset (für Systeme ohne Wazuh-Agent)
    """
    # Check for duplicate hostname
    existing_hostname = db.query(Asset).filter(
        Asset.hostname == asset_data.hostname
    ).first()
    if existing_hostname:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Asset with this hostname already exists"
        )

    # Check for duplicate IP (only if IP is provided)
    if asset_data.ip_address:
        existing_ip = db.query(Asset).filter(
            Asset.ip_address == asset_data.ip_address
        ).first()
        if existing_ip:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Asset with this IP address already exists"
            )

    # Erstelle Asset
    asset = Asset(
        **asset_data.model_dump(),
        source=AssetSource.MANUAL,
        status=AssetStatus.ACTIVE
    )

    db.add(asset)
    db.commit()
    db.refresh(asset)

    # Audit-Log
    audit_log = AuditLog(
        user_id=current_user.id,
        event_type=AuditEventType.ASSET_CREATED,
        event_description=f"Manual asset created: {asset.hostname}",
        resource_type="asset",
        resource_id=str(asset.id),
        timestamp=datetime.now()
    )
    db.add(audit_log)
    db.commit()

    asset.vulnerability_count = 0
    return asset


@router.put("/{asset_id}", response_model=AssetResponse)
async def update_asset(
    asset_id: int,
    update_data: AssetUpdateRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """Aktualisiert Asset-Daten"""
    asset = db.query(Asset).filter(Asset.id == asset_id).first()

    if not asset:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Asset not found"
        )

    # Update nur gesetzte Felder
    import json as _json
    for field, value in update_data.model_dump(exclude_unset=True).items():
        if field == "compliance_frameworks":
            # Stored as TEXT JSON list
            value = _json.dumps(value or []) if value is not None else None
        if field == "criticality" and value is not None:
            value = value.lower()
            if value not in ("low", "normal", "high", "critical"):
                raise HTTPException(400, "criticality must be one of: low, normal, high, critical")
        setattr(asset, field, value)

    db.commit()
    db.refresh(asset)

    # Audit-Log
    audit_log = AuditLog(
        user_id=current_user.id,
        event_type=AuditEventType.ASSET_UPDATED,
        event_description=f"Asset updated: {asset.hostname}",
        resource_type="asset",
        resource_id=str(asset.id),
        timestamp=datetime.now()
    )
    db.add(audit_log)
    db.commit()

    vuln_count = db.query(Vulnerability).filter(
        Vulnerability.asset_id == asset_id,
        Vulnerability.status == VulnerabilityStatus.open
    ).count()

    asset.vulnerability_count = vuln_count
    return asset


@router.delete("/{asset_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_asset(
    asset_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireAdmin)
):
    """
    Löscht Asset (Admin-only)

    ACHTUNG: Löscht auch alle zugehörigen Vulnerabilities (CASCADE)
    """
    asset = db.query(Asset).filter(Asset.id == asset_id).first()

    if not asset:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Asset not found"
        )

    # Audit-Log vor Löschung
    audit_log = AuditLog(
        user_id=current_user.id,
        event_type=AuditEventType.ASSET_DELETED,
        event_description=f"Asset deleted: {asset.hostname}",
        resource_type="asset",
        resource_id=str(asset.id),
        timestamp=datetime.now()
    )
    db.add(audit_log)

    db.delete(asset)
    db.commit()

    return None


@router.patch("/{asset_id}/assign")
async def assign_asset(
    asset_id: int,
    assign_data: AssetAssignRequest,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """Assigns a user to an asset"""
    asset = db.query(Asset).filter(Asset.id == asset_id).first()
    if not asset:
        raise HTTPException(status_code=404, detail="Asset not found")

    if assign_data.user_id is not None:
        user = db.query(User).filter(User.id == assign_data.user_id).first()
        if not user:
            raise HTTPException(status_code=404, detail="User not found")
    
    asset.assigned_user_id = assign_data.user_id

    # Handle single group assignment (legacy support)
    from app.models.group import Group
    if assign_data.group_id is None:
        # Clear groups when explicitly set to null
        asset.groups = []
    else:
        group = db.query(Group).filter(Group.id == assign_data.group_id).first()
        if group:
            asset.groups = [group]
    
    db.commit()
    db.refresh(asset)

    vuln_count = db.query(Vulnerability).filter(
        Vulnerability.asset_id == asset_id,
        Vulnerability.status == VulnerabilityStatus.open
    ).count()
    asset.vulnerability_count = vuln_count
    asset.assigned_user_name = asset.assigned_user.username if asset.assigned_user else None
    return asset


@router.get("/{asset_id}/vulnerabilities")
async def get_asset_vulnerabilities(
    asset_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """Alle Vulnerabilities eines Assets"""
    asset = db.query(Asset).filter(Asset.id == asset_id).first()

    if not asset:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Asset not found"
        )

    vulns = db.query(Vulnerability).filter(
        Vulnerability.asset_id == asset_id
    ).all()

    return [{
        "id": v.id,
        "cve_id": v.cve_id,
        "severity": v.severity,
        "status": v.status,
        "cvss_score": v.cvss_score,
        "package_name": v.package_name,
        "detected_at": v.detected_at
    } for v in vulns]

# --- Wazuh Sync Endpoint ---
import json
from app.integrations.wazuh_client import WazuhClient, WazuhAPIError
from app.models.setting import Setting

@router.post("/sync_wazuh", response_model=dict)
async def sync_wazuh_assets(
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """
    Synchronizes assets from Wazuh Manager.
    fetch active agents -> update/create local assets.
    """
    # 1. Get Wazuh Config (transparently decrypted)
    from app.auth.setting_crypto import read_setting_value
    raw_wazuh = read_setting_value(db, "wazuh_config")
    if not raw_wazuh:
         raise HTTPException(status_code=400, detail="Wazuh configuration not found.")

    try:
        config = json.loads(raw_wazuh)
        api_url = config.get("api_url")
        username = config.get("username")
        password = config.get("password")
    except json.JSONDecodeError:
         raise HTTPException(status_code=500, detail="Invalid Wazuh configuration format.")

    if not all([api_url, username, password]):
         raise HTTPException(status_code=400, detail="Incomplete Wazuh configuration.")

    indexer_url = config.get("indexer_url")
    indexer_username = config.get("indexer_username")
    indexer_password = config.get("indexer_password")

    stats = {"created": 0, "updated": 0, "total_from_wazuh": 0}

    # 2. Fetch Agents from Wazuh
    try:
        with WazuhClient(
            base_url=api_url, 
            username=username, 
            password=password,
            indexer_url=indexer_url,
            indexer_username=indexer_username,
            indexer_password=indexer_password,
            verify_ssl=config.get("verify_ssl", False)
        ) as client:
            agents = client.get_agents(status="active") # Only sync active agents? or all?
            # Let's get all to be safe, filter by active later if needed
            # agents = client.get_agents() 
            
            stats["total_from_wazuh"] = len(agents)

            for agent in agents:
                agent_id = agent.get("id")
                if agent_id == "000": continue # Skip manager itself

                hostname = agent.get("name")
                ip = agent.get("ip")
                os_info = agent.get("os", {})
                os_name = os_info.get("name")
                os_version = os_info.get("version")
                
                # Check if exists by Wazuh ID
                asset = db.query(Asset).filter(Asset.wazuh_agent_id == agent_id).first()
                
                if not asset:
                    # Check by hostname to avoid duplicates if manually added
                    asset = db.query(Asset).filter(Asset.hostname == hostname).first()

                if asset:
                    # Update
                    asset.wazuh_agent_id = agent_id
                    asset.ip_address = ip or asset.ip_address
                    asset.operating_system = os_name or asset.operating_system
                    asset.os_version = os_version or asset.os_version
                    asset.last_seen = datetime.now()
                    asset.source = AssetSource.WAZUH
                    asset.status = AssetStatus.ACTIVE if agent.get("status") == "active" else AssetStatus.INACTIVE
                    stats["updated"] += 1
                else:
                    # Create
                    # Default Group Logic
                    default_group_setting = db.query(Setting).filter(Setting.key == "default_group_id").first()
                    group_id = int(default_group_setting.value) if default_group_setting and default_group_setting.value else None

                    new_asset = Asset(
                        hostname=hostname,
                        ip_address=ip,
                        wazuh_agent_id=agent_id,
                        operating_system=os_name,
                        os_version=os_version,
                        source=AssetSource.WAZUH,
                        status=AssetStatus.ACTIVE,
                        last_seen=datetime.now()
                    )
                    
                    # Handle default group
                    if group_id:
                        from app.models.group import Group
                        default_group = db.query(Group).filter(Group.id == group_id).first()
                        if default_group:
                            new_asset.groups.append(default_group)

                    db.add(new_asset)
                    stats["created"] += 1
            
            db.commit()

    except Exception as e:
        logger.error(f"Wazuh Sync Failed: {e}")
        raise HTTPException(status_code=500, detail="Wazuh sync failed. Check server logs for details.")

    return stats


@router.post("/{asset_id}/rescan", response_model=dict)
async def rescan_asset(
    asset_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(RequireEditor)
):
    """
    Triggert einen gezielten Rescan eines einzelnen Assets via Wazuh.
    """
    from app.routers.vulnerabilities import sync_agent_vulnerabilities
    
    asset = db.query(Asset).filter(Asset.id == asset_id).first()
    if not asset:
        raise HTTPException(status_code=404, detail="Asset not found")
    
    if asset.source != AssetSource.WAZUH or not asset.wazuh_agent_id:
        raise HTTPException(status_code=400, detail="Rescan only possible for Wazuh-managed assets")

    # 1. Wazuh Config holen (transparently decrypted)
    from app.auth.setting_crypto import read_setting_value
    raw_wazuh = read_setting_value(db, "wazuh_config")
    if not raw_wazuh:
         raise HTTPException(status_code=400, detail="Wazuh configuration missing")

    config = json.loads(raw_wazuh)
    wazuh = WazuhClient(
        base_url=config.get("api_url"), 
        username=config.get("username"), 
        password=config.get("password"),
        indexer_url=config.get("indexer_url"),
        indexer_username=config.get("indexer_username"),
        indexer_password=config.get("indexer_password"),
        verify_ssl=config.get("verify_ssl", False)
    )

    try:
        # Sync Vulnerabilities for this specific agent
        sync_agent_vulnerabilities(db, wazuh, asset.wazuh_agent_id, asset)
        
        # Audit Log
        audit = AuditLog(
            user_id=current_user.id,
            event_type=AuditEventType.SCAN_TRIGGERED,
            event_description=f"Manual rescan started for asset {asset.hostname} (ID: {asset.wazuh_agent_id})",
            resource_type="asset",
            resource_id=str(asset_id),
            timestamp=datetime.now()
        )
        db.add(audit)
        db.commit()
        
        return {"message": f"Rescan for {asset.hostname} successfully completed.", "last_scan": asset.last_scan}
    except Exception as e:
        logger.error(f"Rescan error: {e}")
        raise HTTPException(status_code=500, detail=f"Rescan failed: {str(e)}")
