"""
Weekly scan: Bitbucket repos with commits in the last N days, pull and run SonarQube scan.
Generates a summary report of all pulled and scanned repos (no per-repo PDFs).
"""
import os
import re
import sys
import logging
from pathlib import Path
from datetime import datetime
from typing import Dict, List

try:
    from config_manager import ConfigManager
    from bitbucket_client import BitbucketClient
    from git_manager import GitManager
    from sonarqube_scanner import SonarQubeScanner
except ImportError:
    from .config_manager import ConfigManager
    from .bitbucket_client import BitbucketClient
    from .git_manager import GitManager
    from .sonarqube_scanner import SonarQubeScanner

# Log to file and console; log file set after we know project root
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[logging.StreamHandler(sys.stdout)],
)
logger = logging.getLogger(__name__)


def _get_additional_repositories(config: ConfigManager) -> List[Dict]:
    """Get additional repositories from config (same logic as main.py)."""
    additional_repos_config = config.get("scanning.additional_repositories", [])
    if not additional_repos_config:
        return []

    repos = []
    for repo_config in additional_repos_config:
        if isinstance(repo_config, str):
            if repo_config.startswith("git@bitbucket.org:"):
                path = repo_config.replace("git@bitbucket.org:", "").replace(".git", "")
                parts = path.split("/")
                if len(parts) == 2:
                    workspace, repo_name = parts
                    repos.append({
                        "name": repo_name,
                        "slug": repo_name,
                        "workspace": {"slug": workspace},
                        "links": {"clone": [{"name": "ssh", "href": repo_config}]},
                    })
            elif repo_config.startswith("https://github.com/"):
                path = repo_config.replace("https://github.com/", "").replace(".git", "")
                parts = path.split("/")
                if len(parts) == 2:
                    org, repo_name = parts
                    repos.append({
                        "name": repo_name,
                        "slug": repo_name,
                        "workspace": {"slug": org},
                        "links": {
                            "clone": [
                                {"name": "ssh", "href": repo_config},
                                {"name": "https", "href": repo_config},
                            ]
                        },
                    })
            elif "/" in repo_config:
                parts = repo_config.split("/")
                if len(parts) == 2:
                    workspace, repo_name = parts
                    ssh_url = f"git@bitbucket.org:{workspace}/{repo_name}.git"
                    repos.append({
                        "name": repo_name,
                        "slug": repo_name,
                        "workspace": {"slug": workspace},
                        "links": {"clone": [{"name": "ssh", "href": ssh_url}]},
                    })
        elif isinstance(repo_config, dict):
            repo_name = repo_config.get("name") or repo_config.get("slug")
            workspace = repo_config.get("workspace")
            ssh_url = repo_config.get("ssh_url") or repo_config.get("clone_url")
            if repo_name and ssh_url:
                if isinstance(workspace, str):
                    workspace = {"slug": workspace}
                elif not workspace and "git@bitbucket.org:" in ssh_url:
                    path = ssh_url.replace("git@bitbucket.org:", "").replace(".git", "")
                    ws_name = path.split("/")[0] if "/" in path else None
                    workspace = {"slug": ws_name} if ws_name else {}
                repos.append({
                    "name": repo_name,
                    "slug": repo_name,
                    "workspace": workspace or {},
                    "links": {"clone": [{"name": "ssh", "href": ssh_url}]},
                })
    return repos


def _merge_scan_recent_always_include(config: ConfigManager, all_repos: List[Dict], to_process: List[Dict]) -> None:
    raw = config.get("scanning.scan_recent_always_include") or []
    if isinstance(raw, str):
        raw = [s.strip() for s in raw.split(",") if s.strip()]
    if not raw:
        return
    seen = {
        (r.get("name") or r.get("slug"))
        for r in to_process
        if r.get("name") or r.get("slug")
    }
    for slug in raw:
        if not slug or slug in seen:
            continue
        for repo_info in all_repos:
            rslug = repo_info.get("name") or repo_info.get("slug")
            if rslug == slug:
                to_process.append(repo_info)
                seen.add(slug)
                logger.info(f"Including repo (scan_recent_always_include): {slug}")
                break
        else:
            logger.warning(
                "scan_recent_always_include: slug %r not in repo list — add scanning.additional_repositories entry",
                slug,
            )


def run_weekly_scan() -> None:
    """Run weekly pull and scan workflow."""
    # Resolve project root (parent of src when running as script)
    project_root = Path(__file__).resolve().parent.parent
    if str(project_root) not in sys.path:
        sys.path.insert(0, str(project_root))

    # Ensure we run from project root for relative paths
    os.chdir(project_root)

    # Log file for this run
    log_dir = project_root / "logs"
    log_dir.mkdir(parents=True, exist_ok=True)
    log_ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    log_file = log_dir / f"weekly_scan_{log_ts}.log"
    file_handler = logging.FileHandler(log_file, encoding="utf-8")
    file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
    logging.getLogger().addHandler(file_handler)

    logger.info("Starting weekly pull and scan (repos with commits in last N days)")

    try:
        config = ConfigManager()
    except Exception as e:
        logger.error(f"Failed to load config: {e}")
        sys.exit(1)

    # Validate required config
    if not config.get("bitbucket.workspace"):
        logger.error("Bitbucket workspace not configured")
        sys.exit(1)
    has_api_token = bool(config.get("bitbucket.api_token"))
    has_app_password = bool(config.get("bitbucket.app_password"))
    if not config.get("bitbucket.username"):
        logger.error("Bitbucket username/email not configured")
        sys.exit(1)
    if not has_api_token and not has_app_password:
        logger.error("Bitbucket authentication not configured (need API token or app password)")
        sys.exit(1)
    if not config.get("sonarqube.token"):
        logger.error("SonarQube token not configured")
        sys.exit(1)

    days = config.get("scanning.weekly_scan_days") or 7
    main_workspace = config.get("bitbucket.workspace")
    repos_dir = Path(config.get("scanning.repos_dir"))
    reports_dir = Path(config.get("scanning.reports_dir"))
    reports_dir.mkdir(parents=True, exist_ok=True)

    # Init clients
    bitbucket = BitbucketClient(
        workspace=main_workspace,
        username=config.get("bitbucket.username"),
        app_password=config.get("bitbucket.app_password") if not has_api_token else None,
        api_token=config.get("bitbucket.api_token"),
    )
    git_manager = GitManager(repos_dir=config.get("scanning.repos_dir"))
    sonar_scanner = SonarQubeScanner(
        sonar_url=config.get("sonarqube.url"),
        sonar_token=config.get("sonarqube.token"),
        scanner_path=config.get("scanning.sonar_scanner_path"),
    )

    # Get all repos
    repos = bitbucket.get_all_repositories() or []
    additional = _get_additional_repositories(config)
    if additional:
        logger.info(f"Found {len(additional)} additional repositories from config")
        repos.extend(additional)

    if not repos:
        logger.warning("No repositories found")
        _write_summary(log_file, reports_dir, log_ts, days, [], [], [])
        return

    # Optional whitelist: only these repos (e.g. "Last updated" from Bitbucket UI)
    whitelist = config.get("scanning.weekly_scan_repos") or []
    if isinstance(whitelist, str):
        whitelist = [s.strip() for s in whitelist.split(",") if s.strip()]
    if whitelist:
        whitelist_set = {s.lower() for s in whitelist}
        repos = [r for r in repos if (r.get("name") or r.get("slug") or "").lower() in whitelist_set]
        logger.info(f"Limited to whitelist: {len(repos)} repos ({', '.join(whitelist)})")
        if not repos:
            logger.warning("No repos matched the weekly_scan_repos whitelist")
            _write_summary(log_file, reports_dir, log_ts, days, [], [], [])
            return

    # Filter: repos with commits in last N days (main workspace only); include all additional
    to_process = []
    for repo_info in repos:
        repo_slug = repo_info.get("name") or repo_info.get("slug")
        if not repo_slug:
            continue
        ws_slug = (repo_info.get("workspace") or {}).get("slug")
        if ws_slug and ws_slug != main_workspace:
            # Additional repo: include without commit check
            to_process.append(repo_info)
            logger.info(f"Including additional repo (no commit check): {repo_slug}")
        elif bitbucket.has_commits_in_last_n_days(repo_slug, days=days):
            to_process.append(repo_info)
            logger.info(f"Including repo (commits in last {days} days): {repo_slug}")
    _merge_scan_recent_always_include(config, repos, to_process)
    logger.info(f"Repos to pull and scan: {len(to_process)}")

    # Phase 1: Pull
    pull_results = []
    for repo_info in to_process:
        repo_slug = repo_info.get("name") or repo_info.get("slug")
        clone_url = bitbucket.get_clone_url(repo_info, protocol="ssh")
        if not clone_url:
            logger.warning(f"No SSH clone URL for {repo_slug}, skipping")
            pull_results.append((repo_slug, False, "No clone URL"))
            continue
        try:
            result = git_manager.clone_or_update(repo_slug=repo_slug, clone_url=clone_url)
            ok = result and (isinstance(result, tuple) and result[0])
            pull_results.append((repo_slug, ok, "OK" if ok else "Pull failed"))
            if ok:
                logger.info(f"Pull OK: {repo_slug}")
            else:
                logger.warning(f"Pull failed: {repo_slug}")
        except Exception as e:
            logger.exception(f"Pull error for {repo_slug}: {e}")
            pull_results.append((repo_slug, False, str(e)))

    # Phase 2: Scan
    scan_results = []
    for repo_info in to_process:
        repo_slug = repo_info.get("name") or repo_info.get("slug")
        repo_path = repos_dir / repo_slug
        if not repo_path.exists() or not (repo_path / ".git").exists():
            scan_results.append((repo_slug, False, "Repo missing or not a git repo"))
            continue

        sonar_props_path = git_manager.get_sonar_properties_path(repo_path)
        if not sonar_props_path:
            workspace = repo_info.get("workspace", {}).get("slug") or main_workspace
            project_key = f"{workspace}_{repo_slug}".lower().replace("-", "_").replace(" ", "_")
            project_key = re.sub(r"[^a-z0-9_]", "_", project_key)
            project_name = repo_info.get("name") or repo_slug
            try:
                sonar_props_path = sonar_scanner.generate_sonar_properties(
                    repo_path=repo_path,
                    project_key=project_key,
                    project_name=project_name,
                )
            except Exception as e:
                logger.error(f"Failed to generate sonar-project.properties for {repo_slug}: {e}")
                scan_results.append((repo_slug, False, str(e)))
                continue

        project_key = sonar_scanner.get_project_key(sonar_props_path)
        if not project_key:
            scan_results.append((repo_slug, False, "No project key in sonar-project.properties"))
            continue

        if not sonar_scanner.check_project_exists(project_key):
            project_name = repo_info.get("name") or repo_slug
            description = repo_info.get("description", "") or ""
            sonar_scanner.create_project(
                project_key=project_key,
                project_name=project_name,
                description=description,
            )

        try:
            result = sonar_scanner.run_scan(repo_path, sonar_props_path)
            success = result.get("success", False)
            err = result.get("error", "") if not success else ""
            scan_results.append((repo_slug, success, err or ("OK" if success else "Scan failed")))
            if success:
                logger.info(f"Scan OK: {repo_slug}")
            else:
                logger.warning(f"Scan failed: {repo_slug} - {err}")
        except Exception as e:
            logger.exception(f"Scan error for {repo_slug}: {e}")
            scan_results.append((repo_slug, False, str(e)))

    _write_summary(log_file, reports_dir, log_ts, days, to_process, pull_results, scan_results)
    logger.info("Weekly scan finished. See summary report.")


def _write_summary(
    log_file: Path,
    reports_dir: Path,
    log_ts: str,
    days: int,
    to_process: List[Dict],
    pull_results: List[tuple],
    scan_results: List[tuple],
) -> None:
    """Write summary report to reports dir and append to log."""
    summary_path = reports_dir / f"weekly_scan_summary_{log_ts[:8]}.txt"
    lines = [
        "=" * 60,
        f"Weekly Scan Summary - {datetime.now().isoformat()}",
        f"Lookback: last {days} days",
        f"Repos with commits in window: {len(to_process)}",
        "=" * 60,
        "",
        "Pull results:",
    ]
    pull_ok = sum(1 for _, ok, _ in pull_results if ok)
    for repo_slug, ok, msg in pull_results:
        lines.append(f"  {'OK' if ok else 'FAIL'}: {repo_slug} - {msg}")
    lines.extend(["", f"Pulled: {pull_ok}/{len(pull_results)}", "", "Scan results:"])
    scan_ok = sum(1 for _, ok, _ in scan_results if ok)
    for repo_slug, ok, msg in scan_results:
        lines.append(f"  {'OK' if ok else 'FAIL'}: {repo_slug} - {msg}")
    lines.extend(["", f"Scanned: {scan_ok}/{len(scan_results)}", "=" * 60])
    summary_text = "\n".join(lines)
    summary_path.write_text(summary_text, encoding="utf-8")
    logger.info(f"Summary written to {summary_path}")
    logger.info(f"Log file: {log_file}")


if __name__ == "__main__":
    try:
        run_weekly_scan()
    except KeyboardInterrupt:
        logger.info("Interrupted by user")
        sys.exit(1)
    except Exception as e:
        logger.exception(f"Fatal error: {e}")
        sys.exit(1)
