#!/usr/bin/env python3
"""
ISMS Audit Scheduler

Risk-based audit planning and scheduling for ISO 27001 compliance.
Generates annual audit plans based on control risk ratings.

Usage:
    python isms_audit_scheduler.py --year 2025 --output audit_plan.json
    python isms_audit_scheduler.py --controls controls.csv --format markdown
"""

import argparse
import csv
import json
import sys
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional


# ISO 27001:2022 Annex A control domains
CONTROL_DOMAINS = {
    "A.5": {"name": "Organizational Controls", "count": 37},
    "A.6": {"name": "People Controls", "count": 8},
    "A.7": {"name": "Physical Controls", "count": 14},
    "A.8": {"name": "Technological Controls", "count": 34},
}

# Default risk ratings for control areas
DEFAULT_RISK_RATINGS = {
    "A.5.1": {"name": "Policies for information security", "risk": "medium"},
    "A.5.2": {"name": "Information security roles", "risk": "medium"},
    "A.5.15": {"name": "Access control", "risk": "high"},
    "A.5.24": {"name": "Incident management planning", "risk": "high"},
    "A.5.25": {"name": "Assessment of security events", "risk": "high"},
    "A.6.1": {"name": "Screening", "risk": "medium"},
    "A.6.3": {"name": "Information security awareness", "risk": "medium"},
    "A.6.7": {"name": "Remote working", "risk": "high"},
    "A.7.1": {"name": "Physical security perimeters", "risk": "medium"},
    "A.7.4": {"name": "Physical security monitoring", "risk": "medium"},
    "A.8.2": {"name": "Privileged access rights", "risk": "critical"},
    "A.8.5": {"name": "Secure authentication", "risk": "critical"},
    "A.8.7": {"name": "Protection against malware", "risk": "high"},
    "A.8.8": {"name": "Management of vulnerabilities", "risk": "critical"},
    "A.8.13": {"name": "Information backup", "risk": "high"},
    "A.8.15": {"name": "Logging", "risk": "critical"},
    "A.8.20": {"name": "Networks security", "risk": "high"},
    "A.8.24": {"name": "Use of cryptography", "risk": "high"},
}

# Audit frequency based on risk level
AUDIT_FREQUENCY = {
    "critical": 4,  # Quarterly
    "high": 2,      # Semi-annual
    "medium": 1,    # Annual
    "low": 1,       # Annual
}


def load_controls_from_csv(filepath: str) -> Dict[str, Dict]:
    """Load control risk ratings from CSV file."""
    controls = {}
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            reader = csv.DictReader(f)
            for row in reader:
                control_id = row.get("control_id", row.get("id", ""))
                if control_id:
                    controls[control_id] = {
                        "name": row.get("name", "Unknown"),
                        "risk": row.get("risk", "medium").lower(),
                    }
    except FileNotFoundError:
        print(f"Error: File not found: {filepath}", file=sys.stderr)
        sys.exit(1)
    return controls


def calculate_audit_dates(
    year: int,
    frequency: int
) -> List[str]:
    """Calculate audit dates based on frequency."""
    dates = []
    interval = 12 // frequency
    for i in range(frequency):
        month = (i * interval) + 2  # Start in February
        if month > 12:
            month = month - 12
        date = datetime(year, month, 15)
        dates.append(date.strftime("%Y-%m-%d"))
    return dates


def generate_audit_plan(
    year: int,
    controls: Optional[Dict[str, Dict]] = None
) -> Dict[str, Any]:
    """Generate risk-based annual audit plan."""
    if controls is None:
        controls = DEFAULT_RISK_RATINGS

    plan = {
        "metadata": {
            "year": year,
            "generated": datetime.now().isoformat(),
            "methodology": "ISO 27001 Risk-Based Internal Auditing",
            "total_controls": len(controls),
        },
        "schedule": {
            "Q1": {"month": "February-March", "audits": []},
            "Q2": {"month": "May-June", "audits": []},
            "Q3": {"month": "August-September", "audits": []},
            "Q4": {"month": "November", "audits": []},
        },
        "controls": {},
    }

    # Assign controls to quarters based on risk
    for control_id, control_data in controls.items():
        risk = control_data.get("risk", "medium")
        frequency = AUDIT_FREQUENCY.get(risk, 1)
        audit_dates = calculate_audit_dates(year, frequency)

        plan["controls"][control_id] = {
            "name": control_data.get("name", "Unknown"),
            "risk": risk,
            "frequency": frequency,
            "scheduled_audits": audit_dates,
        }

        # Add to quarterly schedule
        for i, date in enumerate(audit_dates):
            month = int(date.split("-")[1])
            if month <= 3:
                quarter = "Q1"
            elif month <= 6:
                quarter = "Q2"
            elif month <= 9:
                quarter = "Q3"
            else:
                quarter = "Q4"

            plan["schedule"][quarter]["audits"].append({
                "control_id": control_id,
                "control_name": control_data.get("name", "Unknown"),
                "risk_level": risk,
                "target_date": date,
            })

    # Sort audits within each quarter
    for quarter in plan["schedule"]:
        plan["schedule"][quarter]["audits"].sort(
            key=lambda x: (
                {"critical": 0, "high": 1, "medium": 2, "low": 3}.get(x["risk_level"], 4),
                x["target_date"]
            )
        )

    # Calculate summary statistics
    risk_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0}
    total_audits = 0
    for control_data in plan["controls"].values():
        risk_counts[control_data["risk"]] += 1
        total_audits += control_data["frequency"]

    plan["summary"] = {
        "total_controls_in_scope": len(controls),
        "total_audits_planned": total_audits,
        "risk_distribution": risk_counts,
        "audits_per_quarter": {
            q: len(plan["schedule"][q]["audits"])
            for q in plan["schedule"]
        },
    }

    return plan


def format_markdown(plan: Dict[str, Any]) -> str:
    """Format audit plan as markdown."""
    lines = [
        f"# ISMS Audit Plan {plan['metadata']['year']}",
        f"",
        f"**Generated:** {plan['metadata']['generated'][:10]}",
        f"**Methodology:** {plan['metadata']['methodology']}",
        f"",
        f"## Summary",
        f"",
        f"| Metric | Value |",
        f"|--------|-------|",
        f"| Controls in Scope | {plan['summary']['total_controls_in_scope']} |",
        f"| Total Audits Planned | {plan['summary']['total_audits_planned']} |",
        f"| Critical Risk Controls | {plan['summary']['risk_distribution']['critical']} |",
        f"| High Risk Controls | {plan['summary']['risk_distribution']['high']} |",
        f"| Medium Risk Controls | {plan['summary']['risk_distribution']['medium']} |",
        f"",
    ]

    for quarter, data in plan["schedule"].items():
        lines.extend([
            f"## {quarter}: {data['month']}",
            f"",
            f"| Control | Name | Risk | Target Date |",
            f"|---------|------|------|-------------|",
        ])
        for audit in data["audits"]:
            lines.append(
                f"| {audit['control_id']} | {audit['control_name']} | "
                f"{audit['risk_level'].capitalize()} | {audit['target_date']} |"
            )
        lines.append("")

    lines.extend([
        f"## Risk-Based Audit Frequency",
        f"",
        f"| Risk Level | Audit Frequency |",
        f"|------------|-----------------|",
        f"| Critical | Quarterly (4x/year) |",
        f"| High | Semi-Annual (2x/year) |",
        f"| Medium | Annual (1x/year) |",
        f"| Low | Annual (1x/year) |",
    ])

    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(
        description="ISMS Audit Scheduler - Risk-based audit planning"
    )
    parser.add_argument(
        "--year", "-y",
        type=int,
        default=datetime.now().year,
        help="Audit plan year (default: current year)"
    )
    parser.add_argument(
        "--controls", "-c",
        help="CSV file with control risk ratings"
    )
    parser.add_argument(
        "--output", "-o",
        help="Output file path"
    )
    parser.add_argument(
        "--format", "-f",
        choices=["json", "markdown"],
        default="json",
        help="Output format (default: json)"
    )

    args = parser.parse_args()

    # Load controls
    controls = None
    if args.controls:
        controls = load_controls_from_csv(args.controls)

    # Generate plan
    plan = generate_audit_plan(args.year, controls)

    # Format output
    if args.format == "markdown":
        output = format_markdown(plan)
    else:
        output = json.dumps(plan, indent=2)

    # Write output
    if args.output:
        with open(args.output, "w", encoding="utf-8") as f:
            f.write(output)
        print(f"Audit plan saved to: {args.output}", file=sys.stderr)
    else:
        print(output)


if __name__ == "__main__":
    main()
