"""
JWT License Generator - Generate JWT-based license keys for testing and development.

[20251225_FEATURE] v3.3.0 - License generation utility for development/testing

This module provides utilities to generate JWT license keys for Code Scalpel.
In production, these would be generated by a secure licensing server.

SECURITY NOTE: The private key in this file is for DEVELOPMENT/TESTING ONLY.
Production licenses should be signed with a secure private key stored in a
key management system (AWS KMS, HashiCorp Vault, etc.).

Usage:
    # Generate a Pro license valid for 1 year
    python -m code_scalpel.licensing.jwt_generator \\
        --tier pro \\
        --customer "customer_12345" \\
        --organization "Acme Corp" \\
        --duration 365 \\
        --output .scalpel-license

    # Generate an Enterprise license with specific features
    python -m code_scalpel.licensing.jwt_generator \\
        --tier enterprise \\
        --customer "customer_67890" \\
        --organization "Big Tech Inc" \\
        --features cognitive_complexity context_aware_scanning formal_verification \\
        --seats 50 \\
        --duration 730

    # Generate a test license for CI/CD
    python -m code_scalpel.licensing.jwt_generator \\
        --tier pro \\
        --customer "ci_test" \\
        --duration 3650 \\
        --algorithm HS256 \\
        --secret "test_secret_key_12345"
"""

from __future__ import annotations

import argparse
import sys
import uuid
from datetime import datetime, timedelta, timezone
from pathlib import Path

try:
    import jwt

    JWT_AVAILABLE = True
except ImportError:
    JWT_AVAILABLE = False
    print("ERROR: PyJWT not installed. Install with: pip install PyJWT cryptography")
    sys.exit(1)


# [20251227_SECURITY] Do not ship private keys inside the package.
# RS256 license generation must use an explicitly provided private key
# (ideally from a separate issuer tool/service backed by KMS/HSM).

# Keep generator defaults aligned with the runtime validator defaults.
# This ensures locally minted CI/dev tokens validate without requiring
# environment overrides.
DEFAULT_LICENSE_ISSUER = "code-scalpel"
DEFAULT_LICENSE_AUDIENCE = "code-scalpel"

# Feature lists for each tier
COMMUNITY_FEATURES = []

PRO_FEATURES = [
    "cognitive_complexity",
    "code_smell_detection",
    "context_aware_scanning",
    "sanitizer_recognition",
    "framework_entrypoint_detection",
    "usage_categorization",
    "interface_resolution",
    "reachability_analysis",
]

ENTERPRISE_FEATURES = PRO_FEATURES + [
    "formal_verification",
    "equivalence_checking",
    "compliance_audit",
    "custom_policy_engine",
    "incremental_indexing",
    "architectural_firewall",
    "graph_query_language",
    "runtime_enhanced_call_graph",
    "bug_reproduction",
    "regression_prediction",
]


def generate_license(
    tier: str,
    customer_id: str,
    organization: str | None = None,
    features: list[str] | None = None,
    duration_days: int = 365,
    seats: int | None = None,
    algorithm: str = "RS256",
    private_key: str | None = None,
    secret_key: str | None = None,
    issuer: str = DEFAULT_LICENSE_ISSUER,
    audience: str = DEFAULT_LICENSE_AUDIENCE,
    jti: str | None = None,
    kid: str | None = None,
    backdate_seconds: int = 10,
) -> str:
    """
    Generate a JWT license token.

    Args:
        tier: License tier ("community", "pro", "enterprise")
        customer_id: Unique customer identifier
        organization: Organization name
        features: List of enabled features (auto-detected if None)
        duration_days: License validity in days
        seats: Number of seats (for enterprise)
        algorithm: JWT algorithm ("RS256" or "HS256")
        private_key: RSA private key (for RS256)
        secret_key: Shared secret (for HS256)

    Returns:
        JWT token string
    """
    # Auto-detect features based on tier
    if features is None:
        if tier == "community":
            features = COMMUNITY_FEATURES
        elif tier == "pro":
            features = PRO_FEATURES
        elif tier == "enterprise":
            features = ENTERPRISE_FEATURES
        else:
            features = []

    # Calculate timestamps
    # [20251228_BUGFIX] Use timezone-aware UTC datetimes. Using a naive UTC
    # datetime with .timestamp() applies the local timezone offset and can
    # yield iat/nbf values in the future, causing remote verifiers to reject
    # the token as "not yet valid (iat)".
    now = datetime.now(timezone.utc)
    expiration = now + timedelta(days=duration_days)
    # [20251228_BUGFIX] Backdate IAT/NBF to tolerate verifier clock skew in
    # dockerized/local environments.
    issued_at = now - timedelta(seconds=backdate_seconds)

    # [20251227_SECURITY] Include standard JWT claims used by production validators.
    # [20251228_BUGFIX] Include both legacy (sub/organization) and verifier
    # schema (customer/org/seats) claims so locally minted licenses validate
    # against the verification service.
    effective_seats = seats if seats is not None else 1
    claims = {
        "iss": issuer,
        "aud": audience,
        "sub": customer_id,
        "customer": customer_id,
        "tier": tier,
        "features": features,
        "seats": int(effective_seats),
        "jti": jti or str(uuid.uuid4()),
        "exp": int(expiration.timestamp()),
        "iat": int(issued_at.timestamp()),
        "nbf": int(issued_at.timestamp()),
    }

    if organization:
        claims["organization"] = organization
        claims["org"] = organization
    else:
        claims["org"] = customer_id

    # seats is always present via effective_seats

    headers = {}
    if kid:
        headers["kid"] = kid

    # Sign the token
    if algorithm == "RS256":
        if not private_key:
            raise ValueError(
                "RS256 license generation requires an explicit private_key. "
                "Do not embed private keys in this repo/package."
            )
        token = jwt.encode(claims, private_key, algorithm="RS256", headers=headers)
    elif algorithm == "HS256":
        if not secret_key:
            raise ValueError("HS256 algorithm requires a secret_key")
        token = jwt.encode(claims, secret_key, algorithm="HS256", headers=headers)
    else:
        raise ValueError(f"Unsupported algorithm: {algorithm}")

    return token


def main():
    """CLI entry point for license generation."""
    parser = argparse.ArgumentParser(description="Generate JWT license keys for Code Scalpel")

    parser.add_argument(
        "--tier",
        required=True,
        choices=["community", "pro", "enterprise"],
        help="License tier",
    )

    parser.add_argument("--customer", required=True, help="Customer ID (unique identifier)")

    parser.add_argument("--organization", help="Organization name")

    parser.add_argument(
        "--features",
        nargs="*",
        help="Specific features to enable (auto-detected if not specified)",
    )

    parser.add_argument(
        "--duration",
        type=int,
        default=365,
        help="License duration in days (default: 365)",
    )

    parser.add_argument("--seats", type=int, help="Number of seats (for enterprise tier)")

    parser.add_argument(
        "--backdate-seconds",
        type=int,
        default=10,
        help=("Backdate iat/nbf by this many seconds to tolerate clock skew " "(default: 10)"),
    )

    parser.add_argument(
        "--algorithm",
        choices=["RS256", "HS256"],
        default="RS256",
        help="JWT signing algorithm (default: RS256)",
    )

    parser.add_argument("--secret", help="Secret key for HS256 algorithm (required for HS256)")

    parser.add_argument(
        "--private-key",
        help="Path to RSA private key (required for RS256; private keys must not ship in the package)",
    )

    parser.add_argument(
        "--issuer",
        default=DEFAULT_LICENSE_ISSUER,
        help=f"JWT issuer claim (default: {DEFAULT_LICENSE_ISSUER})",
    )

    parser.add_argument(
        "--audience",
        default=DEFAULT_LICENSE_AUDIENCE,
        help=f"JWT audience claim (default: {DEFAULT_LICENSE_AUDIENCE})",
    )

    parser.add_argument(
        "--kid",
        help="Optional key id (kid) header for key rotation",
    )

    parser.add_argument(
        "--output",
        type=str,
        default=".scalpel-license",
        help="Output file path (default: .scalpel-license)",
    )

    parser.add_argument(
        "--print",
        action="store_true",
        help="Print token to stdout instead of saving to file",
    )

    args = parser.parse_args()

    # Validate HS256 requirements
    if args.algorithm == "HS256" and not args.secret:
        parser.error("HS256 algorithm requires --secret")

    # [20251227_SECURITY] Require explicit private key for RS256.
    if args.algorithm == "RS256" and not args.private_key:
        parser.error("RS256 algorithm requires --private-key")

    # Load private key if specified
    private_key = None
    if args.private_key:
        try:
            private_key = Path(args.private_key).read_text()
        except Exception as e:
            print(f"ERROR: Failed to read private key: {e}", file=sys.stderr)
            sys.exit(1)

    # Generate license
    try:
        token = generate_license(
            tier=args.tier,
            customer_id=args.customer,
            organization=args.organization,
            features=args.features,
            duration_days=args.duration,
            seats=args.seats,
            algorithm=args.algorithm,
            private_key=private_key,
            secret_key=args.secret,
            issuer=args.issuer,
            audience=args.audience,
            kid=args.kid,
            backdate_seconds=args.backdate_seconds,
        )

        # Output
        if args.print:
            print(token)
        else:
            output_path = Path(args.output)
            output_path.write_text(token)
            print(f"License written to: {output_path.absolute()}")

            # Print summary
            print("\nLicense Summary:")
            print(f"  Tier: {args.tier}")
            print(f"  Customer: {args.customer}")
            if args.organization:
                print(f"  Organization: {args.organization}")
            print(f"  Duration: {args.duration} days")
            if args.seats:
                print(f"  Seats: {args.seats}")
            print(f"  Algorithm: {args.algorithm}")

            # Decode and show expiration
            claims = jwt.decode(token, options={"verify_signature": False})
            # [20251228_BUGFIX] Avoid deprecated datetime.utcfromtimestamp().
            exp_date = datetime.fromtimestamp(claims["exp"], tz=timezone.utc).replace(tzinfo=None)
            print(f"  Expires: {exp_date.strftime('%Y-%m-%d %H:%M:%S')} UTC")

    except Exception as e:
        print(f"ERROR: Failed to generate license: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
