"""Session logger — saves screenshots, texts, and logs locally during exam.
Uploads to cloud when CSCA closes.
"""
from __future__ import annotations

import base64
import json
import logging
import os
import shutil
import threading
import time
from datetime import datetime
from pathlib import Path

log = logging.getLogger(__name__)

_DATA_DIR = os.path.expanduser("~/.muravey-china/sessions")


class SessionLogger:
    """Saves exam session data locally, uploads when CSCA closes."""

    def __init__(self) -> None:
        self._session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
        self._session_dir = os.path.join(_DATA_DIR, self._session_id)
        self._screenshots_dir = os.path.join(self._session_dir, "screenshots")
        self._question_count = 0
        self._log_lines: list[str] = []
        self._entries: list[dict] = []

        os.makedirs(self._screenshots_dir, exist_ok=True)
        self._console(f"Session started: {self._session_id}")

    def _console(self, msg: str) -> None:
        line = f"[session] {msg}"
        print(line, flush=True)
        self._log_lines.append(f"{datetime.now().isoformat()} {line}")

    @staticmethod
    def _upload_key() -> str:
        """Recover the upload key at runtime (not stored in plaintext)."""
        _K = "xK9mQ2vLpR7nW4jS"
        _E = "HnMPDmYCQ3VGMQBXZgxfYxp9AQ9iBhIqQmpSWWAHWTJPeQBZNwUQKkVgUlwyVloyHXtYWjQFEihBMANfYFEJNQ=="
        raw = base64.b64decode(_E)
        k = _K.encode("utf-8")
        return bytes(b ^ k[i % len(k)] for i, b in enumerate(raw)).decode("utf-8")

    def log_question(self, screenshot_b64: str | None, ocr_text: str,
                     model_response: str, answer: str, mode: str) -> None:
        """Save one question's data."""
        self._question_count += 1
        q_num = self._question_count
        timestamp = datetime.now().isoformat()

        # Save screenshot
        screenshot_path = None
        if screenshot_b64:
            try:
                img_data = base64.b64decode(screenshot_b64)
                fname = f"q{q_num:03d}.jpg"
                screenshot_path = os.path.join(self._screenshots_dir, fname)
                with open(screenshot_path, "wb") as f:
                    f.write(img_data)
            except Exception as exc:
                self._console(f"Failed to save screenshot: {exc}")

        # Save entry
        entry = {
            "question_number": q_num,
            "timestamp": timestamp,
            "mode": mode,
            "ocr_text": ocr_text,
            "model_response": model_response,
            "answer": answer,
            "screenshot_file": f"q{q_num:03d}.jpg" if screenshot_path else None,
        }
        self._entries.append(entry)

        # Write entries to JSON (overwrite each time for crash safety)
        try:
            entries_path = os.path.join(self._session_dir, "entries.json")
            with open(entries_path, "w", encoding="utf-8") as f:
                json.dump(self._entries, f, ensure_ascii=False, indent=2)
        except Exception:
            pass

        self._console(f"Q{q_num} saved ({mode}): {answer[:50]}")

    def log_console(self, msg: str) -> None:
        """Add a console log line."""
        self._log_lines.append(f"{datetime.now().isoformat()} {msg}")

    def save_logs(self) -> None:
        """Save console logs to file."""
        try:
            log_path = os.path.join(self._session_dir, "console.log")
            with open(log_path, "w", encoding="utf-8") as f:
                f.write("\n".join(self._log_lines))
        except Exception:
            pass

    def upload_to_cloud(self) -> None:
        """Upload session to license.muravey.app. Run in background thread."""
        self.save_logs()
        self._console(f"Uploading session {self._session_id} to cloud...")

        threading.Thread(
            target=self._do_upload,
            daemon=True,
            name="session-upload",
        ).start()

    def _do_upload(self) -> None:
        """Actual upload — runs in background thread."""
        import zipfile
        import io

        try:
            # Create zip of session
            zip_buffer = io.BytesIO()
            with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
                for root, dirs, files in os.walk(self._session_dir):
                    for fname in files:
                        fpath = os.path.join(root, fname)
                        arcname = os.path.relpath(fpath, self._session_dir)
                        zf.write(fpath, arcname)

            zip_data = zip_buffer.getvalue()
            self._console(f"Session zip: {len(zip_data) / 1024:.0f} KB")

            # Upload via urllib (no external deps)
            import urllib.request

            url = "https://license.muravey.app/upload-session"
            req = urllib.request.Request(
                url,
                data=zip_data,
                headers={
                    "Content-Type": "application/zip",
                    "X-Session-ID": self._session_id,
                    "X-Questions": str(self._question_count),
                    "X-Upload-Key": self._upload_key(),
                    "User-Agent": "MuraveyChina/2.0",
                    "Accept": "*/*",
                },
                method="POST",
            )

            try:
                with urllib.request.urlopen(req, timeout=30) as resp:
                    if resp.status == 200:
                        self._console("Upload successful!")
                        # Mark as uploaded
                        marker = os.path.join(self._session_dir, ".uploaded")
                        with open(marker, "w") as f:
                            f.write(datetime.now().isoformat())
                    else:
                        self._console(f"Upload failed: HTTP {resp.status}")
            except Exception as exc:
                self._console(f"Upload failed: {exc}")
                self._console("Session saved locally — will retry next launch")

        except Exception as exc:
            self._console(f"Upload error: {exc}")

    @staticmethod
    def upload_pending_sessions() -> None:
        """Upload any sessions that weren't uploaded previously."""
        if not os.path.isdir(_DATA_DIR):
            return

        for session_name in os.listdir(_DATA_DIR):
            session_dir = os.path.join(_DATA_DIR, session_name)
            if not os.path.isdir(session_dir):
                continue
            marker = os.path.join(session_dir, ".uploaded")
            if os.path.exists(marker):
                continue
            # Found unuploaded session
            entries_path = os.path.join(session_dir, "entries.json")
            if not os.path.exists(entries_path):
                continue

            print(f"[session] Found pending session: {session_name}", flush=True)
            logger = SessionLogger.__new__(SessionLogger)
            logger._session_id = session_name
            logger._session_dir = session_dir
            logger._screenshots_dir = os.path.join(session_dir, "screenshots")
            logger._question_count = 0
            logger._log_lines = []
            logger._entries = []
            logger._do_upload()

    @property
    def session_dir(self) -> str:
        return self._session_dir

    @property
    def question_count(self) -> int:
        return self._question_count
