#!/usr/bin/env python3
"""
Batch CRM Termination Script
=============================

This script automates the process of terminating user accounts in CRM by:
1. Reading EmployeeIDs from an Excel file or accepting a single EmployeeID
2. Querying Active Directory for user information
3. Finding the user in CRM and verifying EmployeeId
4. Disabling the user account in CRM
5. Generating a detailed report

Usage:
    # Single user mode
    python terminate_users.py --employee-id 162727 [--what-if]

    # Excel batch mode
    python terminate_users.py --excel AdTermination.xlsx [--what-if]

    # Other options
    --config-file PATH      # Configuration file (default: config.json)
    --report-path PATH      # Report output directory (default: ./reports)
    --headless              # Run browser in headless mode
    --use-session FILE      # Use saved session instead of logging in
"""

import argparse
import asyncio
import csv
import json
import logging
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional

# Import our modules
import openpyxl
from openpyxl import load_workbook

# Import custom modules
from dpapi_crypto import decrypt_dpapi_password
from ad_query import query_ad_by_employee_id, ADQueryError
from crm_api import CRMClient, CRMAPIError
from playwright_login import login_to_crm, CRMLoginError, load_session_from_file

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout)
    ]
)
logger = logging.getLogger(__name__)


class TerminationResult:
    """Class to store termination result for each user"""

    def __init__(
        self,
        employee_id: str,
        display_name: str = "",
        status: str = "pending",
        message: str = "",
        crm_user_id: str = ""
    ):
        self.employee_id = employee_id
        self.display_name = display_name
        self.status = status  # success, failed, skipped
        self.message = message
        self.crm_user_id = crm_user_id
        self.timestamp = datetime.now().isoformat()


class TerminationConfig:
    """Configuration for termination process"""

    def __init__(self, config_file: str = "config.json"):
        self.config_file = config_file
        self.load_config()

    def load_config(self):
        """Load configuration from JSON file"""
        if not os.path.exists(self.config_file):
            logger.warning(f"Config file not found: {self.config_file}, using defaults")
            self.config = {
                "crm": {
                    "url": "https://internalcrm.macausjm-glp.com",
                    "username": ""
                },
                "ad": {
                    "domain": "macausjm-glp.com"
                },
                "credentials": {
                    "password_file": "password.enc"
                },
                "processing": {
                    "max_retries": 3,
                    "timeout": 30
                }
            }
        else:
            with open(self.config_file, 'r') as f:
                self.config = json.load(f)

    def get(self, *keys, default=None):
        """Get nested configuration value"""
        value = self.config
        for key in keys:
            if isinstance(value, dict):
                value = value.get(key)
                if value is None:
                    return default
            else:
                return default
        return value if value is not None else default


def read_excel_employee_ids(file_path: str) -> List[str]:
    """
    Read EmployeeIDs from Excel file.

    Assumes EmployeeID is in the first column or a column named "EmployeeID".

    Args:
        file_path: Path to Excel file

    Returns:
        list: List of EmployeeID strings
    """
    if not os.path.exists(file_path):
        raise FileNotFoundError(f"Excel file not found: {file_path}")

    try:
        workbook = load_workbook(file_path, read_only=True)
        sheet = workbook.active

        employee_ids = []

        # Look for EmployeeID column header
        headers = []
        for cell in sheet[1]:
            headers.append(cell.value)

        employee_id_col = None
        for idx, header in enumerate(headers):
            if header and str(header).strip().upper() == "EMPLOYEEID":
                employee_id_col = idx + 1  # 1-indexed
                break

        # If no header found, use first column
        if not employee_id_col:
            employee_id_col = 1
            logger.warning("No 'EmployeeID' column header found, using first column")

        # Read EmployeeIDs (skip header row)
        for row in sheet.iter_rows(min_row=2, min_col=employee_id_col, max_col=employee_id_col):
            cell_value = row[0].value
            if cell_value:
                employee_ids.append(str(cell_value).strip())

        workbook.close()

        logger.info(f"Read {len(employee_ids)} EmployeeIDs from {file_path}")
        return employee_ids

    except Exception as e:
        raise Exception(f"Error reading Excel file: {e}")


def generate_report(results: List[TerminationResult], report_path: str):
    """
    Generate CSV report of termination results.

    Args:
        results: List of TerminationResult objects
        report_path: Directory to save report
    """
    os.makedirs(report_path, exist_ok=True)

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    report_file = os.path.join(report_path, f"Termination_Report_{timestamp}.csv")

    with open(report_file, 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow([
            'EmployeeID',
            'DisplayName',
            'CRMUserId',
            'Status',
            'Message',
            'Timestamp'
        ])

        for result in results:
            writer.writerow([
                result.employee_id,
                result.display_name,
                result.crm_user_id,
                result.status,
                result.message,
                result.timestamp
            ])

    logger.info(f"Report generated: {report_file}")

    # Print summary
    total = len(results)
    success = sum(1 for r in results if r.status == "success")
    failed = sum(1 for r in results if r.status == "failed")
    skipped = sum(1 for r in results if r.status == "skipped")

    print("\n" + "="*60)
    print("TERMINATION SUMMARY")
    print("="*60)
    print(f"Total Users:     {total}")
    print(f"Successful:      {success}")
    print(f"Failed:          {failed}")
    print(f"Skipped:         {skipped}")
    print("="*60)
    print(f"\nReport saved to: {report_file}")


async def terminate_user(
    employee_id: str,
    ad_domain: str,
    crm_client: CRMClient,
    max_retries: int = 3,
    use_fuzzy_match: bool = True
) -> TerminationResult:
    """
    Terminate a single user.

    Args:
        employee_id: Employee ID to terminate
        ad_domain: Active Directory domain
        crm_client: CRM API client
        max_retries: Maximum retry attempts for CRM operations
        use_fuzzy_match: Whether to use fuzzy matching for display names

    Returns:
        TerminationResult: Result of termination
    """
    result = TerminationResult(employee_id)

    try:
        logger.info(f"\nProcessing EmployeeId: {employee_id}")
        print(f"\n{'='*60}")
        print(f"Processing: {employee_id}")
        print('='*60)

        # Step 1: Query AD
        print(f"[1/3] Querying Active Directory...")
        try:
            user_info = query_ad_by_employee_id(employee_id, ad_domain)
            display_name = user_info['DisplayName']
            result.display_name = display_name
            print(f"  Found: {display_name} ({user_info['SamAccountName']})")
        except ADQueryError as e:
            result.status = "failed"
            result.message = f"AD query failed: {e}"
            print(f"  ERROR: {e}")
            return result

        # Step 2: Find user in CRM (with optional display name variant fallback)
        print(f"[2/3] Finding user in CRM...")
        if use_fuzzy_match:
            print(f"  Mode: Fuzzy matching enabled (will try variants with/without -d)")
        else:
            print(f"  Mode: Exact matching only")
        try:
            # Use fuzzy matching or exact matching based on parameter
            if use_fuzzy_match:
                user_data = crm_client.find_user_by_display_name_and_employee_id_with_fallback(
                    display_name,
                    employee_id
                )
            else:
                user_data = crm_client.find_user_by_display_name_and_employee_id(
                    display_name,
                    employee_id
                )
            systemuserid = user_data['systemuserid']
            result.crm_user_id = systemuserid
            print(f"  Found: CRM User ID = {systemuserid}")
        except CRMAPIError as e:
            result.status = "failed"
            result.message = f"CRM query failed: {e}"
            print(f"  ERROR: {e}")
            return result

        # Step 3: Disable user in CRM
        print(f"[3/3] Disabling user in CRM...")
        success, message = crm_client.disable_user_with_retry(systemuserid, max_retries)

        if success:
            result.status = "success"
            result.message = message
            print(f"  SUCCESS: {message}")
        else:
            result.status = "failed"
            result.message = message
            print(f"  FAILED: {message}")

        return result

    except Exception as e:
        result.status = "failed"
        result.message = f"Unexpected error: {e}"
        logger.error(f"Error processing {employee_id}: {e}", exc_info=True)
        return result


async def main():
    """Main function"""

    # Parse command line arguments
    parser = argparse.ArgumentParser(
        description='Batch CRM Termination Script',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Terminate single user
  python terminate_users.py --employee-id 162727

  # What-if mode (dry run)
  python terminate_users.py --employee-id 162727 --what-if

  # Batch process from Excel
  python terminate_users.py --excel AdTermination.xlsx

  # Use saved session instead of logging in
  python terminate_users.py --excel AdTermination.xlsx --use-session crm_session.json

  # Run in headless mode
  python terminate_users.py --employee-id 162727 --headless
        """
    )

    parser.add_argument(
        '--employee-id',
        type=str,
        help='Single Employee ID to terminate'
    )
    parser.add_argument(
        '--excel',
        type=str,
        help='Excel file containing EmployeeIDs to terminate'
    )
    parser.add_argument(
        '--config-file',
        type=str,
        default='config.json',
        help='Configuration file path (default: config.json)'
    )
    parser.add_argument(
        '--report-path',
        type=str,
        default='./reports',
        help='Report output directory (default: ./reports)'
    )
    parser.add_argument(
        '--headless',
        action='store_true',
        help='Run browser in headless mode'
    )
    parser.add_argument(
        '--incognito',
        action='store_true',
        help='Use incognito mode (no cache, no cookies saved to disk)'
    )
    parser.add_argument(
        '--use-session',
        type=str,
        help='Use saved session file instead of logging in'
    )
    parser.add_argument(
        '--what-if',
        action='store_true',
        help='Simulation mode - do not actually disable users'
    )
    parser.add_argument(
        '--domain',
        type=str,
        help='Override AD domain from config'
    )
    parser.add_argument(
        '--max-retries',
        type=int,
        help='Override max retries for CRM operations'
    )
    parser.add_argument(
        '--no-fuzzy-match',
        action='store_true',
        help='Disable display name fuzzy matching (do not try variants with/without -d suffix)'
    )

    args = parser.parse_args()

    # Validate arguments
    if not args.employee_id and not args.excel:
        parser.error("Either --employee-id or --excel must be specified")

    if args.employee_id and args.excel:
        parser.error("Cannot specify both --employee-id and --excel")

    # Load configuration
    config = TerminationConfig(args.config_file)

    # Get settings
    crm_url = config.get("crm", "url", default="https://internalcrm.macausjm-glp.com")
    crm_username = config.get("crm", "username", default="")
    password_file = config.get("credentials", "password_file", default="password.enc")
    ad_domain = args.domain or config.get("ad", "domain", default="macausjm-glp.com")
    max_retries = args.max_retries or config.get("processing", "max_retries", default=3)
    timeout = config.get("processing", "timeout", default=30)

    # What-if mode
    if args.what_if:
        print("\n" + "!"*60)
        print("WHAT-IF MODE: No actual changes will be made")
        print("!"*60 + "\n")

    try:
        # Get CRM session cookies
        cookies = None

        if args.use_session:
            # Load from saved session
            print(f"\nLoading session from: {args.use_session}")
            cookies = load_session_from_file(args.use_session)

            if not cookies:
                print(f"ERROR: Failed to load session file: {args.use_session}")
                sys.exit(1)

            print(f"Session loaded successfully")
        else:
            # Login using Playwright
            print("\n" + "="*60)
            print("CRM LOGIN")
            print("="*60)

            # Get credentials
            if not crm_username:
                crm_username = input("Enter CRM username (e.g., DOMAIN\\user.name): ").strip()

            try:
                password = decrypt_dpapi_password(password_file)
                print(f"Password loaded from: {password_file}")
            except Exception as e:
                print(f"ERROR: Failed to decrypt password: {e}")
                password = input("Enter CRM password: ").strip()

            print(f"\nLogging in to: {crm_url}")
            print(f"Username: {crm_username}")
            print(f"Headless: {args.headless}")
            print(f"Incognito: {args.incognito}\n")

            cookies = await login_to_crm(
                crm_url,
                crm_username,
                password,
                headless=args.headless,
                incognito=args.incognito,
                timeout=timeout * 1000
            )

            print("\nLogin successful!")

        # Create CRM client
        crm_client = CRMClient(crm_url, cookies, timeout=timeout)

        # Get list of EmployeeIDs to process
        employee_ids = []

        if args.employee_id:
            employee_ids = [args.employee_id]
        else:
            print("\n" + "="*60)
            print("READING EXCEL FILE")
            print("="*60)
            employee_ids = read_excel_employee_ids(args.excel)

        if not employee_ids:
            print("ERROR: No EmployeeIDs to process")
            sys.exit(1)

        print(f"\nTotal users to process: {len(employee_ids)}")

        if args.what_if:
            print("\nWHAT-IF: Would process the following users:")
            for eid in employee_ids:
                print(f"  - {eid}")
            sys.exit(0)

        # Process each user
        results = []

        print("\n" + "="*60)
        print("PROCESSING USERS")
        print("="*60)

        for idx, employee_id in enumerate(employee_ids, 1):
            print(f"\n[{idx}/{len(employee_ids)}]")

            result = await terminate_user(
                employee_id,
                ad_domain,
                crm_client,
                max_retries,
                use_fuzzy_match=not args.no_fuzzy_match
            )

            results.append(result)

            # Stop on first failure (as per requirements)
            if result.status == "failed":
                logger.error(f"Termination failed for {employee_id}. Stopping process.")
                print("\n" + "!"*60)
                print("TERMINATION STOPPED DUE TO ERROR")
                print("!"*60)
                break

        # Generate report
        print("\n" + "="*60)
        print("GENERATING REPORT")
        print("="*60)

        generate_report(results, args.report_path)

    except KeyboardInterrupt:
        print("\n\nInterrupted by user")
        sys.exit(1)
    except CRMLoginError as e:
        print(f"\nERROR: CRM login failed: {e}")
        sys.exit(1)
    except Exception as e:
        logger.error(f"Unexpected error: {e}", exc_info=True)
        print(f"\nERROR: {e}")
        sys.exit(1)


if __name__ == "__main__":
    asyncio.run(main())
