"""
Active Directory Query Module
==============================

This module provides functionality to query Active Directory users
using PowerShell's Get-ADUser command via subprocess.
"""

import subprocess
import json
import re
from typing import Optional, Dict
import logging

logger = logging.getLogger(__name__)


class ADQueryError(Exception):
    """Exception raised for AD query errors"""
    pass


def query_ad_by_employee_id(employee_id: str, domain: str = "macausjm-glp.com") -> Dict[str, str]:
    """
    Query Active Directory for a user by EmployeeId.

    This function uses PowerShell's Get-ADUser cmdlet to query Active Directory
    for a user with the specified EmployeeId and returns their display name and
    other relevant information.

    Args:
        employee_id: The employee ID to search for
        domain: The AD domain name (default: macausjm-glp.com)

    Returns:
        dict: A dictionary containing:
            - DisplayName: User's display name
            - SamAccountName: User's login name
            - UserPrincipalName: User's principal name (UPN)
            - EmployeeId: The employee ID
            - DistinguishedName: User's distinguished name

    Raises:
        ADQueryError: If the user is not found or query fails

    Example:
        >>> user_info = query_ad_by_employee_id("162727")
        >>> print(user_info['DisplayName'])
        'John Doe'
    """
    # Validate employee_id
    if not employee_id or not str(employee_id).strip():
        raise ADQueryError("Employee ID cannot be empty")

    employee_id = str(employee_id).strip()

    # Build PowerShell command
    ps_command = [
        "powershell",
        "-Command",
        f"Import-Module ActiveDirectory; "
        f"Get-ADUser -Filter \"EmployeeId -eq '{employee_id}'\" "
        f"-Properties DisplayName, SamAccountName, UserPrincipalName, EmployeeId, DistinguishedName "
        f"| ConvertTo-Json"
    ]

    try:
        # Execute PowerShell command
        logger.info(f"Querying AD for EmployeeId: {employee_id}")

        result = subprocess.run(
            ps_command,
            capture_output=True,
            text=True,
            timeout=30,
            encoding='utf-8'
        )

        # Check for PowerShell errors
        if result.stderr:
            error_output = result.stderr.strip()
            if "Cannot find an object with identity" in error_output or "Cannot find a user with identity" in error_output:
                raise ADQueryError(
                    f"User with EmployeeId '{employee_id}' not found in Active Directory"
                )
            elif "The term 'Get-ADUser' is not recognized" in error_output:
                raise ADQueryError(
                    "Active Directory module not available. "
                    "Please run this script on a domain controller or a machine with RSAT installed."
                )
            elif "Access is denied" in error_output:
                raise ADQueryError(
                    "Access denied when querying Active Directory. "
                    "Please ensure you have appropriate permissions."
                )
            else:
                logger.warning(f"PowerShell stderr output: {error_output}")

        # Parse JSON output
        if not result.stdout.strip():
            raise ADQueryError(
                f"User with EmployeeId '{employee_id}' not found in Active Directory (no output)"
            )

        try:
            user_data = json.loads(result.stdout)
        except json.JSONDecodeError as e:
            # If JSON parsing fails, try to extract information from text output
            if "Find-ADUser" in result.stdout or "Get-ADUser" in result.stdout:
                raise ADQueryError(
                    f"Error parsing AD query result for EmployeeId '{employee_id}': {e}"
                )
            raise ADQueryError(
                f"Invalid response from Active Directory query for EmployeeId '{employee_id}'"
            )

        # Validate that we got user data
        if not user_data or not isinstance(user_data, dict):
            raise ADQueryError(
                f"User with EmployeeId '{employee_id}' not found in Active Directory (empty result)"
            )

        # Extract required fields
        display_name = user_data.get('DisplayName')
        sam_account_name = user_data.get('SamAccountName')
        user_principal_name = user_data.get('UserPrincipalName')
        distinguished_name = user_data.get('DistinguishedName')
        returned_employee_id = user_data.get('EmployeeId')

        # Validate required fields
        if not display_name:
            raise ADQueryError(
                f"User found but DisplayName is missing for EmployeeId '{employee_id}'"
            )

        if not sam_account_name:
            raise ADQueryError(
                f"User found but SamAccountName is missing for EmployeeId '{employee_id}'"
            )

        # Verify EmployeeId matches (if returned by AD)
        if returned_employee_id and str(returned_employee_id) != employee_id:
            logger.warning(
                f"EmployeeId mismatch: requested '{employee_id}', got '{returned_employee_id}'"
            )

        # Build result
        user_info = {
            'DisplayName': display_name,
            'SamAccountName': sam_account_name,
            'UserPrincipalName': user_principal_name or '',
            'EmployeeId': employee_id,
            'DistinguishedName': distinguished_name or ''
        }

        logger.info(f"Found user: {display_name} ({sam_account_name})")
        return user_info

    except subprocess.TimeoutExpired:
        raise ADQueryError(
            f"Timeout while querying Active Directory for EmployeeId '{employee_id}'"
        )
    except FileNotFoundError:
        raise ADQueryError(
            "PowerShell not found. Please ensure PowerShell is installed and accessible."
        )
    except Exception as e:
        if isinstance(e, ADQueryError):
            raise
        raise ADQueryError(
            f"Unexpected error querying Active Directory for EmployeeId '{employee_id}': {e}"
        )


def query_ad_by_username(username: str, domain: str = "macausjm-glp.com") -> Dict[str, str]:
    """
    Query Active Directory for a user by username (SamAccountName).

    Args:
        username: The username (SamAccountName) to search for
        domain: The AD domain name (default: macausjm-glp.com)

    Returns:
        dict: User information dictionary

    Raises:
        ADQueryError: If the user is not found or query fails
    """
    if not username or not str(username).strip():
        raise ADQueryError("Username cannot be empty")

    username = str(username).strip()

    # Build PowerShell command
    ps_command = [
        "powershell",
        "-Command",
        f"Import-Module ActiveDirectory; "
        f"Get-ADUser -Identity \"{username}\" "
        f"-Properties DisplayName, SamAccountName, UserPrincipalName, EmployeeId, DistinguishedName "
        f"| ConvertTo-Json"
    ]

    try:
        logger.info(f"Querying AD for username: {username}")

        result = subprocess.run(
            ps_command,
            capture_output=True,
            text=True,
            timeout=30,
            encoding='utf-8'
        )

        if result.stderr:
            error_output = result.stderr.strip()
            if "Cannot find an object with identity" in error_output or "Cannot find a user with identity" in error_output:
                raise ADQueryError(
                    f"User '{username}' not found in Active Directory"
                )
            elif "Access is denied" in error_output:
                raise ADQueryError(
                    "Access denied when querying Active Directory. "
                    "Please ensure you have appropriate permissions."
                )
            elif "The term 'Get-ADUser' is not recognized" not in error_output:
                logger.warning(f"PowerShell stderr output: {error_output}")

        if not result.stdout.strip():
            raise ADQueryError(
                f"User '{username}' not found in Active Directory"
            )

        try:
            user_data = json.loads(result.stdout)
        except json.JSONDecodeError:
            raise ADQueryError(
                f"Error parsing AD query result for username '{username}'"
            )

        if not user_data or not isinstance(user_data, dict):
            raise ADQueryError(
                f"User '{username}' not found in Active Directory"
            )

        display_name = user_data.get('DisplayName')
        sam_account_name = user_data.get('SamAccountName')
        user_principal_name = user_data.get('UserPrincipalName')
        employee_id = user_data.get('EmployeeId')
        distinguished_name = user_data.get('DistinguishedName')

        if not display_name:
            raise ADQueryError(
                f"User found but DisplayName is missing for username '{username}'"
            )

        user_info = {
            'DisplayName': display_name,
            'SamAccountName': sam_account_name or username,
            'UserPrincipalName': user_principal_name or '',
            'EmployeeId': employee_id or '',
            'DistinguishedName': distinguished_name or ''
        }

        logger.info(f"Found user: {display_name} ({sam_account_name})")
        return user_info

    except subprocess.TimeoutExpired:
        raise ADQueryError(
            f"Timeout while querying Active Directory for username '{username}'"
        )
    except FileNotFoundError:
        raise ADQueryError(
            "PowerShell not found. Please ensure PowerShell is installed and accessible."
        )
    except Exception as e:
        if isinstance(e, ADQueryError):
            raise
        raise ADQueryError(
            f"Unexpected error querying Active Directory for username '{username}': {e}"
        )


if __name__ == "__main__":
    # Test the AD query module
    import sys

    logging.basicConfig(level=logging.INFO)

    if len(sys.argv) < 2:
        print("Usage: python ad_query.py <employee_id> [domain]")
        print("   or: python ad_query.py --username <username> [domain]")
        sys.exit(1)

    if sys.argv[1] == "--username":
        if len(sys.argv) < 3:
            print("Error: Username not provided")
            sys.exit(1)
        username = sys.argv[2]
        domain = sys.argv[3] if len(sys.argv) > 3 else "macausjm-glp.com"
        try:
            user_info = query_ad_by_username(username, domain)
            print(f"\nUser Information:")
            print(f"  Display Name: {user_info['DisplayName']}")
            print(f"  Username: {user_info['SamAccountName']}")
            print(f"  UPN: {user_info['UserPrincipalName']}")
            print(f"  Employee ID: {user_info['EmployeeId']}")
            print(f"  DN: {user_info['DistinguishedName']}")
        except ADQueryError as e:
            print(f"Error: {e}")
            sys.exit(1)
    else:
        employee_id = sys.argv[1]
        domain = sys.argv[2] if len(sys.argv) > 2 else "macausjm-glp.com"
        try:
            user_info = query_ad_by_employee_id(employee_id, domain)
            print(f"\nUser Information:")
            print(f"  Display Name: {user_info['DisplayName']}")
            print(f"  Username: {user_info['SamAccountName']}")
            print(f"  UPN: {user_info['UserPrincipalName']}")
            print(f"  Employee ID: {user_info['EmployeeId']}")
            print(f"  DN: {user_info['DistinguishedName']}")
        except ADQueryError as e:
            print(f"Error: {e}")
            sys.exit(1)
