"""
CLI commands for authority ledger management.

Provides commands for querying authority ledger events and exporting audit reports.

Requirements: 11.7, 11.9, 11.10
"""

import csv
import json
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional
from uuid import UUID

import click

from caracal.exceptions import CaracalError
from caracal.logging_config import get_logger

logger = get_logger(__name__)


def parse_datetime(date_str: str) -> datetime:
    """
    Parse datetime string in various formats.
    
    Supports:
    - ISO 8601: 2024-01-15T10:30:00Z
    - Date only: 2024-01-15 (assumes 00:00:00)
    - Date and time: 2024-01-15 10:30:00
    
    Args:
        date_str: Date/time string to parse
        
    Returns:
        datetime object
        
    Raises:
        ValueError: If date string cannot be parsed
    """
    # Try ISO 8601 format first
    for fmt in [
        "%Y-%m-%dT%H:%M:%SZ",
        "%Y-%m-%dT%H:%M:%S",
        "%Y-%m-%d %H:%M:%S",
        "%Y-%m-%d",
    ]:
        try:
            return datetime.strptime(date_str, fmt)
        except ValueError:
            continue
    
    raise ValueError(
        f"Invalid date format: {date_str}. "
        f"Expected formats: YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, or ISO 8601"
    )


@click.command('query')
@click.option(
    '--principal-id',
    '-p',
    default=None,
    help='Filter by principal ID (optional)',
)
@click.option(
    '--mandate-id',
    '-m',
    default=None,
    help='Filter by mandate ID (optional)',
)
@click.option(
    '--event-type',
    '-t',
    type=click.Choice(['issued', 'validated', 'denied', 'revoked'], case_sensitive=False),
    default=None,
    help='Filter by event type (optional)',
)
@click.option(
    '--start',
    '-s',
    default=None,
    help='Start time (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)',
)
@click.option(
    '--end',
    '-e',
    default=None,
    help='End time (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)',
)
@click.option(
    '--format',
    '-f',
    type=click.Choice(['table', 'json'], case_sensitive=False),
    default='table',
    help='Output format (default: table)',
)
@click.pass_context
def query(
    ctx,
    principal_id: Optional[str],
    mandate_id: Optional[str],
    event_type: Optional[str],
    start: Optional[str],
    end: Optional[str],
    format: str,
):
    """
    Query authority ledger events with optional filters.
    
    Returns all events matching the specified filters. All filters are optional
    and can be combined.
    
    Examples:
    
        # Query all events
        caracal ledger query
        
        # Query events for a specific principal
        caracal ledger query --principal-id 550e8400-e29b-41d4-a716-446655440000
        
        # Query events for a specific mandate
        caracal ledger query --mandate-id 660e8400-e29b-41d4-a716-446655440001
        
        # Query events by type
        caracal ledger query --event-type validated
        
        # Query events in a date range
        caracal ledger query --start 2024-01-01 --end 2024-01-31
        
        # Combine filters
        caracal ledger query -p <principal-id> -t denied -s 2024-01-01 -e 2024-01-31
        
        # JSON output
        caracal ledger query --format json
    
    Requirements: 11.7, 11.10
    """
    try:
        # Get CLI context
        cli_ctx = ctx.obj
        
        # Parse UUIDs if provided
        principal_uuid = None
        mandate_uuid = None
        
        if principal_id:
            try:
                principal_uuid = UUID(principal_id)
            except ValueError as e:
                click.echo(f"Error: Invalid principal ID format: {e}", err=True)
                sys.exit(1)
        
        if mandate_id:
            try:
                mandate_uuid = UUID(mandate_id)
            except ValueError as e:
                click.echo(f"Error: Invalid mandate ID format: {e}", err=True)
                sys.exit(1)
        
        # Parse date/time filters
        start_time = None
        end_time = None
        
        if start:
            try:
                start_time = parse_datetime(start)
            except ValueError as e:
                click.echo(f"Error: Invalid start time: {e}", err=True)
                sys.exit(1)
        
        if end:
            try:
                end_time = parse_datetime(end)
            except ValueError as e:
                click.echo(f"Error: Invalid end time: {e}", err=True)
                sys.exit(1)
        
        # Validate time range
        if start_time and end_time and start_time > end_time:
            click.echo(
                "Error: Start time must be before or equal to end time",
                err=True
            )
            sys.exit(1)
        
        # Create database connection
        from caracal.db.connection import DatabaseConfig, DatabaseConnectionManager
        from caracal.core.authority_ledger import AuthorityLedgerQuery
        
        db_config = DatabaseConfig(
            host=cli_ctx.config.database.host,
            port=cli_ctx.config.database.port,
            database=cli_ctx.config.database.database,
            user=cli_ctx.config.database.user,
            password=cli_ctx.config.database.password
        )
        db_manager = DatabaseConnectionManager(db_config)
        db_manager.initialize()
        
        try:
            # Create ledger query
            ledger_query = AuthorityLedgerQuery(db_manager.get_session())
            
            # Query events
            events = ledger_query.get_events(
                principal_id=principal_uuid,
                mandate_id=mandate_uuid,
                event_type=event_type.lower() if event_type else None,
                start_time=start_time,
                end_time=end_time
            )
            
            if not events:
                click.echo("No events found matching the specified filters.")
                return
            
            if format.lower() == 'json':
                # JSON output
                output = [
                    {
                        'event_id': e.event_id,
                        'event_type': e.event_type,
                        'timestamp': e.timestamp.isoformat(),
                        'principal_id': str(e.principal_id),
                        'mandate_id': str(e.mandate_id) if e.mandate_id else None,
                        'decision': e.decision,
                        'denial_reason': e.denial_reason,
                        'requested_action': e.requested_action,
                        'requested_resource': e.requested_resource
                    }
                    for e in events
                ]
                click.echo(json.dumps(output, indent=2))
            else:
                # Table output
                click.echo(f"Total events: {len(events)}")
                click.echo()
                
                # Print header
                click.echo(f"{'Event ID':<10}  {'Type':<10}  {'Timestamp':<20}  {'Principal ID':<38}  {'Decision':<10}")
                click.echo("-" * 110)
                
                # Print events
                for e in events:
                    # Format timestamp
                    timestamp_str = e.timestamp.strftime("%Y-%m-%d %H:%M:%S")
                    
                    # Format decision
                    decision_str = e.decision if e.decision else "-"
                    
                    click.echo(
                        f"{e.event_id:<10}  "
                        f"{e.event_type:<10}  "
                        f"{timestamp_str:<20}  "
                        f"{str(e.principal_id):<38}  "
                        f"{decision_str:<10}"
                    )
        
        finally:
            # Close database connection
            db_manager.close()
    
    except CaracalError as e:
        click.echo(f"Error: {e}", err=True)
        sys.exit(1)
    except Exception as e:
        click.echo(f"Unexpected error: {e}", err=True)
        logger.error(f"Failed to query authority ledger: {e}", exc_info=True)
        sys.exit(1)


@click.command('export')
@click.option(
    '--start',
    '-s',
    required=True,
    help='Start time (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)',
)
@click.option(
    '--end',
    '-e',
    required=True,
    help='End time (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)',
)
@click.option(
    '--principal-id',
    '-p',
    default=None,
    help='Filter by principal ID (optional)',
)
@click.option(
    '--output',
    '-o',
    required=True,
    type=click.Path(dir_okay=False, writable=True, path_type=Path),
    help='Output file path',
)
@click.option(
    '--format',
    '-f',
    type=click.Choice(['csv', 'json'], case_sensitive=False),
    default='csv',
    help='Output format (default: csv)',
)
@click.pass_context
def export(
    ctx,
    start: str,
    end: str,
    principal_id: Optional[str],
    output: Path,
    format: str,
):
    """
    Export audit report for a time range.
    
    Generates an audit report containing all authority ledger events
    in the specified time range.
    
    Examples:
    
        # Export CSV report
        caracal audit export \\
            --start 2024-01-01 \\
            --end 2024-01-31 \\
            --output audit_report.csv
        
        # Export JSON report for a specific principal
        caracal audit export \\
            -s 2024-01-01 -e 2024-01-31 \\
            -p 550e8400-e29b-41d4-a716-446655440000 \\
            -o audit_report.json \\
            --format json
    
    Requirements: 11.9, 11.10
    """
    try:
        # Get CLI context
        cli_ctx = ctx.obj
        
        # Parse principal ID if provided
        principal_uuid = None
        if principal_id:
            try:
                principal_uuid = UUID(principal_id)
            except ValueError as e:
                click.echo(f"Error: Invalid principal ID format: {e}", err=True)
                sys.exit(1)
        
        # Parse date/time filters
        try:
            start_time = parse_datetime(start)
            end_time = parse_datetime(end)
        except ValueError as e:
            click.echo(f"Error: {e}", err=True)
            sys.exit(1)
        
        # Validate time range
        if start_time > end_time:
            click.echo(
                "Error: Start time must be before or equal to end time",
                err=True
            )
            sys.exit(1)
        
        # Create database connection
        from caracal.db.connection import DatabaseConfig, DatabaseConnectionManager
        from caracal.core.authority_ledger import AuthorityLedgerQuery
        
        db_config = DatabaseConfig(
            host=cli_ctx.config.database.host,
            port=cli_ctx.config.database.port,
            database=cli_ctx.config.database.database,
            user=cli_ctx.config.database.user,
            password=cli_ctx.config.database.password
        )
        db_manager = DatabaseConnectionManager(db_config)
        db_manager.initialize()
        
        try:
            # Create ledger query
            ledger_query = AuthorityLedgerQuery(db_manager.get_session())
            
            # Query events
            events = ledger_query.get_events(
                principal_id=principal_uuid,
                start_time=start_time,
                end_time=end_time
            )
            
            if not events:
                click.echo("No events found in the specified time range.")
                return
            
            # Export to file
            if format.lower() == 'csv':
                # CSV export
                with open(output, 'w', newline='') as csvfile:
                    fieldnames = [
                        'event_id', 'event_type', 'timestamp', 'principal_id',
                        'mandate_id', 'decision', 'denial_reason',
                        'requested_action', 'requested_resource'
                    ]
                    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
                    
                    writer.writeheader()
                    for e in events:
                        writer.writerow({
                            'event_id': e.event_id,
                            'event_type': e.event_type,
                            'timestamp': e.timestamp.isoformat(),
                            'principal_id': str(e.principal_id),
                            'mandate_id': str(e.mandate_id) if e.mandate_id else '',
                            'decision': e.decision if e.decision else '',
                            'denial_reason': e.denial_reason if e.denial_reason else '',
                            'requested_action': e.requested_action if e.requested_action else '',
                            'requested_resource': e.requested_resource if e.requested_resource else ''
                        })
            else:
                # JSON export
                output_data = {
                    'report_generated': datetime.utcnow().isoformat(),
                    'time_range': {
                        'start': start_time.isoformat(),
                        'end': end_time.isoformat()
                    },
                    'principal_id': str(principal_uuid) if principal_uuid else None,
                    'total_events': len(events),
                    'events': [
                        {
                            'event_id': e.event_id,
                            'event_type': e.event_type,
                            'timestamp': e.timestamp.isoformat(),
                            'principal_id': str(e.principal_id),
                            'mandate_id': str(e.mandate_id) if e.mandate_id else None,
                            'decision': e.decision,
                            'denial_reason': e.denial_reason,
                            'requested_action': e.requested_action,
                            'requested_resource': e.requested_resource
                        }
                        for e in events
                    ]
                }
                
                with open(output, 'w') as jsonfile:
                    json.dump(output_data, jsonfile, indent=2)
            
            click.echo(f"✓ Audit report exported successfully!")
            click.echo()
            click.echo(f"File:         {output}")
            click.echo(f"Format:       {format.upper()}")
            click.echo(f"Events:       {len(events)}")
            click.echo(f"Time Range:   {start_time} to {end_time}")
        
        finally:
            # Close database connection
            db_manager.close()
    
    except CaracalError as e:
        click.echo(f"Error: {e}", err=True)
        sys.exit(1)
    except Exception as e:
        click.echo(f"Unexpected error: {e}", err=True)
        logger.error(f"Failed to export audit report: {e}", exc_info=True)
        sys.exit(1)
