import argparse
import logging
import sys
import os
from pathlib import Path
from typing import List, Optional

# Fix encodage Windows pour les emojis
if sys.platform == 'win32':
    # Forcer UTF-8 pour stdout/stderr sur Windows
    if sys.stdout.encoding != 'utf-8':
        sys.stdout.reconfigure(encoding='utf-8', errors='replace')
    if sys.stderr.encoding != 'utf-8':
        sys.stderr.reconfigure(encoding='utf-8', errors='replace')

from .gui.i18n import localization
from .license.runtime_bridge import get_license_bridge, initialize_license_system
from .model import Finding, ScanRequest
from .reporting import HistoryStore, ReportEngine
from .scanner import SentinelScanner
from .subscription import SubscriptionTier

LANGUAGE_CHOICES = tuple(localization.get_available_languages().keys())


def build_parser() -> argparse.ArgumentParser:
    history_default = str(Path.home() / ".web-sentinel" / "history.json")
    log_default = str(Path.home() / ".web-sentinel" / "web-sentinel.log")

    parser = argparse.ArgumentParser(
        description=localization.t("cli_description")
    )
    parser.add_argument("domain", help=localization.t("cli_arg_domain"))
    parser.add_argument(
        "--language",
        choices=LANGUAGE_CHOICES,
        default=localization.get_current_language(),
        help=localization.t("cli_arg_language").format(default=localization.get_current_language()),
    )
    parser.add_argument(
        "--http-port",
        type=int,
        default=80,
        help=localization.t("cli_arg_http_port").format(default=80),
    )
    parser.add_argument(
        "--https-port",
        type=int,
        default=443,
        help=localization.t("cli_arg_https_port").format(default=443),
    )
    parser.add_argument(
        "--timeout",
        type=float,
        default=5.0,
        help=localization.t("cli_arg_timeout").format(default=5.0),
    )
    parser.add_argument(
        "--allow-invasive",
        action="store_true",
        help=localization.t("cli_arg_allow_invasive"),
    )
    parser.add_argument(
        "--json",
        action="store_true",
        dest="as_json",
        help=localization.t("cli_arg_json"),
    )
    parser.add_argument(
        "--json-report",
        type=str,
        help=localization.t("cli_arg_json_report"),
    )
    parser.add_argument(
        "--html-report",
        type=str,
        help=localization.t("cli_arg_html_report"),
    )
    parser.add_argument(
        "--modules",
        nargs="+",
        help=localization.t("cli_arg_modules"),
    )
    parser.add_argument(
        "--history-file",
        type=str,
        default=history_default,
        help=localization.t("cli_arg_history_file").format(default=history_default),
    )
    parser.add_argument(
        "--no-history",
        action="store_true",
        help=localization.t("cli_arg_no_history"),
    )
    parser.add_argument(
        "--log-file",
        type=str,
        default=log_default,
        help=localization.t("cli_arg_log_file").format(default=log_default),
    )
    parser.add_argument(
        "--api-key",
        type=str,
        help="API key for license validation (format: ws_live_...)",
    )
    
    # SAST (Source Code Analysis) arguments
    parser.add_argument(
        "--source-path",
        type=str,
        help="Path to source code directory or file for static analysis (requires PRO+ license)",
    )
    parser.add_argument(
        "--source-languages",
        nargs="+",
        choices=['php', 'javascript', 'python', 'java', 'csharp', 'go', 'auto'],
        default=['auto'],
        help="Programming languages to scan (default: auto-detect)",
    )
    parser.add_argument(
        "--source-exclude",
        nargs="+",
        default=[],
        help="Patterns to exclude from source code scanning (e.g., vendor/ node_modules/)",
    )
    parser.add_argument(
        "--severity-min",
        choices=['info', 'low', 'medium', 'high', 'critical'],
        default='medium',
        help="Minimum severity level to report (default: medium)",
    )
    
    return parser


def main(argv: Optional[List[str]] = None) -> int:
    pre_parser = argparse.ArgumentParser(add_help=False)
    pre_parser.add_argument("--language", choices=LANGUAGE_CHOICES)
    pre_args, _ = pre_parser.parse_known_args(argv)
    if pre_args.language:
        localization.set_language(pre_args.language)

    parser = build_parser()
    args = parser.parse_args(argv)
    localization.set_language(args.language)

    _configure_logging(args.log_file)
    
    # 🔐 Validation de la clé API si fournie
    if args.api_key:
        if not args.api_key.startswith("ws_live_"):
            logging.error("❌ Invalid API key format. Must start with 'ws_live_'")
            return 1
        
        # Valider la clé via l'API
        license_valid, license_info = _validate_api_key(args.api_key)
        if not license_valid:
            logging.error(f"❌ API key validation failed: {license_info}")
            return 1
        
        logging.info(f"✅ API key validated successfully")
        logging.info(f"   Tier: {license_info.get('tier', 'UNKNOWN')}")
        logging.info(f"   Email: {license_info.get('email', 'N/A')}")
        logging.info(f"   Scans remaining: {license_info.get('remaining_scans', 'unlimited')}")
        
        # Appliquer les limites de la licence API
        tier_map = {
            'STARTER': SubscriptionTier.PRO,
            'PRO': SubscriptionTier.ENTERPRISE,
            'ENTERPRISE': SubscriptionTier.SYSOP
        }
        api_tier = tier_map.get(license_info.get('tier', 'STARTER'), SubscriptionTier.PRO)
    else:
        api_tier = None
    
    # 🔐 PRIORITÉ 1 : Validation licence au démarrage CLI
    logging.info(localization.t("messages.license_initializing"))
    license_bridge = get_license_bridge()
    license_init_success = initialize_license_system()

    # Skip GUI auth pour environnements headless (serveurs Linux)
    subscription_manager = None
    try:
        from .auth.auth_manager import AuthManager
        auth_manager = AuthManager()
        subscription_manager = auth_manager.get_subscription_manager()
        current_user = auth_manager.get_current_user()
        owner_email = current_user.email if current_user else None
        license_init_success = license_bridge.apply_subscription(subscription_manager.tier, owner_email)
    except (ImportError, ModuleNotFoundError, FileNotFoundError) as e:
        logging.warning(f"GUI auth non disponible (environnement headless): {e}")
        logging.info("Mode CLI serveur - auth désactivée, utiliser --api-key pour fonctionnalités PRO")
        # Créer un subscription_manager fake pour FREE tier
        class FakeSubscriptionManager:
            class SubscriptionTier:
                FREE = "FREE"
                PRO = "PRO"
                ENTERPRISE = "ENTERPRISE"
                SYSOP = "SYSOP"
            def __init__(self):
                self.tier = self.SubscriptionTier.FREE
        subscription_manager = FakeSubscriptionManager()
        owner_email = None
        license_init_success = True
    
    license_status = license_bridge.get_license_status()

    is_sysop = subscription_manager.tier == SubscriptionTier.SYSOP

    if license_init_success:
        logging.info(localization.t("cli_license_loaded", license_status.get("tier", "FREE")))
    else:
        if not is_sysop:  # Ne pas afficher l'erreur pour SysOp
            logging.warning(localization.t("cli_license_fallback"))

    alert_level = license_status.get("alert_level")
    threshold = license_status.get("alert_threshold")
    if alert_level == "CRITICAL" and not is_sysop:  # Ignorer les alertes pour SysOp
        if not license_status.get("valid", True):
            logging.error(localization.t("license_status_expired"))
        else:
            logging.error(localization.t("license_status_critical", threshold or 0))
    elif alert_level == "WARNING" and not is_sysop:
        logging.warning(localization.t("license_status_warning", threshold or 0))
    elif alert_level == "INFO" and not is_sysop:
        logging.info(localization.t("license_status_info", threshold or 0))

    features_enabled = license_status.get("features_enabled", {})
    if args.allow_invasive and not features_enabled.get("invasive_tests", False):
        logging.error(localization.t("cli_invasive_not_allowed"))
        args.allow_invasive = False

    if args.html_report and not features_enabled.get("html_export", False):
        logging.error(localization.t("cli_html_not_allowed"))
        return 2

    # Check SAST permissions if source scanning is requested
    if args.source_path and not features_enabled.get("source_scan", False):
        logging.error(localization.t("messages.sast_license_required"))
        logging.info(localization.t("messages.upgrade_for_sast"))
        return 2

    sast_kwargs = {}
    if args.source_path:
        languages = tuple(args.source_languages or ["auto"])
        exclude = tuple(args.source_exclude or [])
        sast_kwargs = {
            "source_path": args.source_path,
            "source_languages": languages,
            "source_exclude": exclude,
            "source_min_severity": args.severity_min,
            "source_max_files": license_status.get("max_source_files", -1),
            "source_max_size_mb": license_status.get("max_source_size_mb", -1),
            "source_advanced_rules": features_enabled.get("advanced_rules", False),
        }

    request = ScanRequest(
        domain=args.domain,
        http_port=args.http_port,
        https_port=args.https_port,
        timeout=args.timeout,
        allow_invasive=args.allow_invasive,
        **sast_kwargs,
    )

    scanner = SentinelScanner()

    enabled_modules = args.modules
    if enabled_modules:
        available = set(scanner.modules.keys())
        unknown = [module for module in enabled_modules if module not in available]
        if unknown:
            logging.warning(localization.t("cli_unknown_modules_warning").format(", ".join(unknown)))
        enabled_modules = [module for module in enabled_modules if module in available]

    # Mesurer durée du scan
    import time
    scan_start = time.time()
    result = scanner.run(request, enabled_modules=enabled_modules)
    scan_duration = time.time() - scan_start

    history_store = None if args.no_history else HistoryStore(Path(args.history_file))
    report_engine = ReportEngine(history_store=history_store)
    context = report_engine.build_context(result)
    
    # 📊 Télémétrie anonyme (optionnelle, désactivable)
    try:
        from .telemetry import send_scan_telemetry
        from collections import Counter
        
        severity_counts = dict(Counter(f.severity for f in result.findings))
        send_scan_telemetry(
            findings_count=len(result.findings),
            scan_duration=scan_duration,
            modules_used=enabled_modules or list(scanner.modules.keys()),
            severity_counts=severity_counts
        )
    except Exception as exc:
        logging.debug("Télémétrie non envoyée (non bloquant): %s", exc)

    if context["regressions"]:
        labels = _format_labels(context["regressions"])
        logging.warning(localization.t("regressions_detected").format(len(context["regressions"]), labels))
    else:
        logging.info(localization.t("no_regressions"))

    if context["resolved"]:
        labels = _format_labels(context["resolved"])
        logging.info(localization.t("fixes_confirmed").format(len(context["resolved"]), labels))

    if history_store:
        history_store.record(result)

    if args.as_json:
        print(report_engine.render_json(context))
        _write_side_reports(report_engine, context, args)
        return 0

    if not result.findings:
        print(localization.t("no_findings"))
        _write_side_reports(report_engine, context, args)
        return 0

    for finding in result.findings:
        translated = localization.translate_finding_content(finding)
        severity_label = localization.translate_severity(finding.severity)
        module_label = _module_label(finding.check)
        title = getattr(translated, "title", finding.title)
        description = getattr(translated, "description", finding.description)
        remediation = getattr(translated, "remediation", finding.remediation)
        evidence = getattr(translated, "evidence", finding.evidence)
        impact = getattr(translated, "impact", finding.impact)

        # Encoder en ASCII avec remplacement des caractères non supportés (fix emoji Windows)
        try:
            print(f"[{severity_label}] {module_label} - {title}")
            print(f"  {localization.t('finding_description')}: {description}")
            print(f"  {localization.t('finding_remediation')}: {remediation}")
            if evidence:
                print(f"  {localization.t('finding_evidence')}: {evidence}")
            if impact:
                print(f"  {localization.t('finding_impact')}: {impact}")
            print()
        except UnicodeEncodeError:
            # Fallback sans emojis pour Windows cmd/PowerShell
            desc_label = localization.t('finding_description').encode('ascii', errors='ignore').decode('ascii')
            rem_label = localization.t('finding_remediation').encode('ascii', errors='ignore').decode('ascii')
            print(f"[{severity_label}] {module_label} - {title}")
            print(f"  {desc_label}: {description}")
            print(f"  {rem_label}: {remediation}")
            if evidence:
                ev_label = localization.t('finding_evidence').encode('ascii', errors='ignore').decode('ascii')
                print(f"  {ev_label}: {evidence}")
            if impact:
                imp_label = localization.t('finding_impact').encode('ascii', errors='ignore').decode('ascii')
                print(f"  {imp_label}: {impact}")
            print()

    _write_side_reports(report_engine, context, args)

    return 0


def _write_side_reports(report_engine: ReportEngine, context: dict, args: argparse.Namespace) -> None:
    if args.json_report:
        path = Path(args.json_report)
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(report_engine.render_json(context), encoding="utf-8")
        logging.info(localization.t("cli_json_saved").format(path))

    if args.html_report:
        path = Path(args.html_report)
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(
            report_engine.render_html(context, language=localization.get_current_language()),
            encoding="utf-8",
        )
        logging.info(localization.t("cli_html_saved").format(path))


def _module_label(module: str) -> str:
    module_key = f"module_{module.replace('-', '_')}"
    label = localization.translate_module(module)
    return label if label != module_key else module


def _format_labels(entries: List[dict]) -> str:
    labels: List[str] = []
    for entry in entries:
        finding = Finding(
            check=entry["check"],
            title=entry["title"],
            severity=entry["severity"],
            description=entry["description"],
            remediation=entry["remediation"],
            impact=entry.get("impact"),
            evidence=entry.get("evidence"),
            i18n_key=entry.get("i18n_key"),
            i18n_params=entry.get("i18n_params"),
        )
        translated = localization.translate_finding_content(finding)
        labels.append(f"{_module_label(finding.check)}::{getattr(translated, 'title', finding.title)}")
    return ", ".join(labels)


def _configure_logging(log_path: Optional[str]) -> None:
    logger = logging.getLogger()
    logger.handlers.clear()
    logger.setLevel(logging.INFO)

    console_handler = logging.StreamHandler()
    console_handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s %(name)s: %(message)s"))
    logger.addHandler(console_handler)

    if log_path:
        path = Path(log_path)
        path.parent.mkdir(parents=True, exist_ok=True)
        file_handler = logging.FileHandler(path, encoding="utf-8")
        file_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
        logger.addHandler(file_handler)


def _validate_api_key(api_key: str) -> tuple[bool, dict]:
    """
    Valide une clé API auprès du serveur de licences.
    
    Returns:
        tuple: (is_valid, license_info_dict)
    """
    import requests
    import os
    
    # URL de l'API (configurable via variable d'environnement)
    api_url = os.getenv("LICENSE_API_URL", "http://localhost:5000")
    endpoint = f"{api_url}/api/v1/license/validate"
    
    try:
        response = requests.post(
            endpoint,
            json={"api_key": api_key},
            timeout=10
        )
        
        if response.status_code == 200:
            data = response.json()
            if data.get("valid"):
                return True, data.get("license", {})
            else:
                return False, data.get("message", "Invalid API key")
        else:
            return False, f"API error: {response.status_code}"
            
    except requests.RequestException as e:
        return False, f"Connection error: {str(e)}"


if __name__ == "__main__":  # pragma: no cover
    sys.exit(main())
