#!/usr/bin/env python3
"""Agent for auditing Azure Active Directory (Entra ID) configuration."""

import os
import json
import argparse
from datetime import datetime, timedelta

from azure.identity import DefaultAzureCredential, ClientSecretCredential
import requests


def get_graph_token(credential):
    """Obtain a Microsoft Graph API access token."""
    token = credential.get_token("https://graph.microsoft.com/.default")
    return token.token


def graph_get(token, endpoint, params=None):
    """Make an authenticated GET request to Microsoft Graph API."""
    headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
    url = f"https://graph.microsoft.com/v1.0{endpoint}"
    resp = requests.get(url, headers=headers, params=params, timeout=30)
    resp.raise_for_status()
    return resp.json()


def get_tenant_info(token):
    """Get tenant organization details."""
    data = graph_get(token, "/organization")
    orgs = data.get("value", [])
    if orgs:
        org = orgs[0]
        return {
            "display_name": org.get("displayName"),
            "tenant_id": org.get("id"),
            "verified_domains": [d["name"] for d in org.get("verifiedDomains", [])],
        }
    return {}


def list_global_admins(token):
    """List all Global Administrator role assignments."""
    roles = graph_get(token, "/directoryRoles")
    ga_role = None
    for role in roles.get("value", []):
        if role["displayName"] == "Global Administrator":
            ga_role = role["id"]
            break
    if not ga_role:
        return []
    members = graph_get(token, f"/directoryRoles/{ga_role}/members")
    return [
        {"displayName": m.get("displayName"), "upn": m.get("userPrincipalName"),
         "type": m.get("@odata.type", "").split(".")[-1]}
        for m in members.get("value", [])
    ]


def list_conditional_access_policies(token):
    """List all Conditional Access policies with their state and grant controls."""
    policies = graph_get(token, "/identity/conditionalAccess/policies")
    results = []
    for p in policies.get("value", []):
        results.append({
            "name": p.get("displayName"),
            "state": p.get("state"),
            "grant_controls": p.get("grantControls", {}).get("builtInControls", []),
            "excluded_groups": p.get("conditions", {}).get("users", {}).get("excludeGroups", []),
        })
    return results


def find_stale_users(token, days=90):
    """Find users who have not signed in for specified number of days."""
    cutoff = (datetime.utcnow() - timedelta(days=days)).strftime("%Y-%m-%dT00:00:00Z")
    users = graph_get(
        token, "/users",
        params={
            "$select": "displayName,userPrincipalName,signInActivity,accountEnabled",
            "$top": "999",
        }
    )
    stale = []
    for u in users.get("value", []):
        sign_in = u.get("signInActivity", {})
        last_sign_in = sign_in.get("lastSignInDateTime")
        if last_sign_in and last_sign_in < cutoff:
            stale.append({
                "upn": u.get("userPrincipalName"),
                "display_name": u.get("displayName"),
                "last_sign_in": last_sign_in,
                "enabled": u.get("accountEnabled"),
            })
    return stale


def list_guest_users(token):
    """List all guest users in the tenant."""
    users = graph_get(
        token, "/users",
        params={"$filter": "userType eq 'Guest'", "$select": "displayName,userPrincipalName,createdDateTime"}
    )
    return [
        {"upn": u.get("userPrincipalName"), "display_name": u.get("displayName"),
         "created": u.get("createdDateTime")}
        for u in users.get("value", [])
    ]


def check_mfa_registration(token):
    """Check users without MFA registered."""
    try:
        data = graph_get(token, "/reports/authenticationMethods/userRegistrationDetails")
        no_mfa = [
            {"upn": u.get("userPrincipalName"), "mfa_registered": u.get("isMfaRegistered")}
            for u in data.get("value", []) if not u.get("isMfaRegistered")
        ]
        return no_mfa
    except Exception:
        return []


def get_risky_signins(token, days=7):
    """Get risky sign-in events from the last N days."""
    since = (datetime.utcnow() - timedelta(days=days)).strftime("%Y-%m-%dT00:00:00Z")
    try:
        data = graph_get(
            token, "/auditLogs/signIns",
            params={"$filter": f"riskLevelDuringSignIn ne 'none' and createdDateTime ge {since}"}
        )
        return [
            {"user": s.get("userPrincipalName"), "risk": s.get("riskLevelDuringSignIn"),
             "ip": s.get("ipAddress"), "app": s.get("appDisplayName")}
            for s in data.get("value", [])
        ]
    except Exception:
        return []


def main():
    parser = argparse.ArgumentParser(description="Azure AD Configuration Audit Agent")
    parser.add_argument("--tenant-id", default=os.getenv("AZURE_TENANT_ID"))
    parser.add_argument("--client-id", default=os.getenv("AZURE_CLIENT_ID"))
    parser.add_argument("--client-secret", default=os.getenv("AZURE_CLIENT_SECRET"))
    parser.add_argument("--stale-days", type=int, default=90)
    parser.add_argument("--output", default="azure_ad_audit.json")
    args = parser.parse_args()

    if args.client_id and args.client_secret and args.tenant_id:
        credential = ClientSecretCredential(args.tenant_id, args.client_id, args.client_secret)
    else:
        credential = DefaultAzureCredential()

    token = get_graph_token(credential)
    report = {"audit_date": datetime.utcnow().isoformat(), "findings": {}}

    report["findings"]["tenant_info"] = get_tenant_info(token)
    print(f"[+] Tenant: {report['findings']['tenant_info'].get('display_name')}")

    admins = list_global_admins(token)
    report["findings"]["global_admins"] = admins
    print(f"[+] Global Admins: {len(admins)}")

    ca_policies = list_conditional_access_policies(token)
    report["findings"]["conditional_access"] = ca_policies
    print(f"[+] Conditional Access policies: {len(ca_policies)}")

    stale = find_stale_users(token, args.stale_days)
    report["findings"]["stale_users"] = stale
    print(f"[+] Stale users ({args.stale_days}+ days): {len(stale)}")

    guests = list_guest_users(token)
    report["findings"]["guest_users"] = guests
    print(f"[+] Guest users: {len(guests)}")

    no_mfa = check_mfa_registration(token)
    report["findings"]["users_without_mfa"] = no_mfa
    print(f"[+] Users without MFA: {len(no_mfa)}")

    risky = get_risky_signins(token)
    report["findings"]["risky_signins"] = risky
    print(f"[+] Risky sign-ins (7d): {len(risky)}")

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
