import hashlib
import json
import os
import shutil
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List

# edited by glg


class DrBackupPolicyService:
    def __init__(self, backup_root: str):
        self.backup_root = Path(str(backup_root or "")).resolve()

    @staticmethod
    def _sha256_file(path: Path) -> str:
        h = hashlib.sha256()
        with path.open("rb") as f:
            for chunk in iter(lambda: f.read(1024 * 1024), b""):
                h.update(chunk)
        return h.hexdigest()

    @staticmethod
    def _safe_copy(src: Path, dst: Path) -> Dict:
        dst.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(str(src), str(dst))
        return {
            "source": str(src),
            "target": str(dst),
            "size": int(dst.stat().st_size if dst.exists() else 0),
            "sha256": DrBackupPolicyService._sha256_file(dst) if dst.exists() else "",
        }

    def run_backup(self, source_paths: List[str], tag: str = "") -> Dict:
        ts = datetime.now().strftime("%Y%m%d_%H%M%S")
        suffix = str(tag or "").strip().replace(" ", "_")
        folder_name = f"{ts}_{suffix}" if suffix else ts
        out_dir = self.backup_root / folder_name
        out_dir.mkdir(parents=True, exist_ok=True)

        copied = []
        missing = []
        for raw in source_paths or []:
            src = Path(str(raw or "")).expanduser().resolve()
            if not src.exists() or not src.is_file():
                missing.append(str(src))
                continue
            dst = out_dir / src.name
            copied.append(self._safe_copy(src, dst))

        manifest = {
            "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "backup_dir": str(out_dir),
            "copied_files": copied,
            "missing_files": missing,
        }
        manifest_path = out_dir / "manifest.json"
        manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
        manifest["manifest_path"] = str(manifest_path)
        return manifest

    def prune_backups(self, keep_last: int = 14, max_age_days: int = 30) -> Dict:
        keep_count = max(1, int(keep_last or 14))
        age_days = max(1, int(max_age_days or 30))
        cutoff = datetime.now() - timedelta(days=age_days)

        if not self.backup_root.exists():
            return {"deleted": [], "kept": []}

        dirs = [p for p in self.backup_root.iterdir() if p.is_dir()]
        dirs.sort(key=lambda p: p.stat().st_mtime, reverse=True)

        kept = []
        deleted = []
        for idx, folder in enumerate(dirs):
            mtime = datetime.fromtimestamp(folder.stat().st_mtime)
            should_delete = idx >= keep_count and mtime < cutoff
            if should_delete:
                shutil.rmtree(folder, ignore_errors=True)
                deleted.append(str(folder))
            else:
                kept.append(str(folder))

        return {"deleted": deleted, "kept": kept}
