"""CTF submission pipeline for jkpfactors.com.

Handles the multi-step HTTP submission workflow:

  1. POST to presign endpoint with submitter info and file manifest
  2. Upload bundle files to S3 via PUT requests
  3. POST to register endpoint to finalise the submission

LIMITATION: jkpfactors.com/ctf/submit uses reCAPTCHA.  Fully automated end-to-end
submission is not possible via a browser-less HTTP client.  If any step returns a
reCAPTCHA challenge (HTTP 4xx with captcha hint), ``submit()`` sets
``manual_steps_required=True``, prints step-by-step instructions, and exits
gracefully without raising.

Usage::

    from eapctf.ctf.package import build_submission
    from eapctf.ctf.submit import submit, SubmissionResult

    bundle = build_submission("model.py", weights_df, "ctf_submission/")
    result = submit(
        bundle,
        submitter={"first_name": "Jane", "last_name": "Doe", "email": "jane@example.com"},
        model_name="My-IPCA-Portfolio",
    )
    print(result)
"""

from __future__ import annotations

import json
import webbrowser
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from eapctf.ctf.package import SubmissionBundle

# ---------------------------------------------------------------------------
# Endpoint constants
# ---------------------------------------------------------------------------

_CTF_SUBMIT_PAGE = "https://jkpfactors.com/ctf/submit"
_CTF_PRESIGN_URL = "https://jkpfactors.com/api/ctf/presign"
_CTF_REGISTER_URL = "https://jkpfactors.com/api/ctf/register"


# ---------------------------------------------------------------------------
# Result dataclass
# ---------------------------------------------------------------------------


@dataclass
class SubmissionResult:
    """Result of a CTF submission attempt.

    Attributes
    ----------
    status : str
        ``"submitted"`` — full programmatic submission completed.
        ``"manual_required"`` — reCAPTCHA or auth blocked automation; files are
        ready and the user must complete submission in a browser.
        ``"error"`` — an unexpected error occurred; see ``error`` field.
    submit_url : str
        URL of the submission form (always populated for reference).
    manual_steps_required : bool
        True when the user must take a manual step to finish submission.
    presign_response : dict | None
        Raw JSON response from the presign endpoint, if reached.
    register_response : dict | None
        Raw JSON response from the register endpoint, if reached.
    uploaded_files : list[str]
        Paths of files successfully uploaded to S3 (before any failure).
    error : str | None
        Error message if ``status == "error"``.
    """

    status: str
    submit_url: str = _CTF_SUBMIT_PAGE
    manual_steps_required: bool = False
    presign_response: dict[str, Any] | None = None
    register_response: dict[str, Any] | None = None
    uploaded_files: list[str] = field(default_factory=list)
    error: str | None = None

    def __str__(self) -> str:
        lines = [
            "=" * 60,
            f"CTF SUBMISSION RESULT  (status: {self.status})",
            "=" * 60,
        ]
        if self.manual_steps_required:
            lines += [
                "",
                "  ACTION REQUIRED — manual browser step needed.",
                f"  Open: {self.submit_url}",
                "",
                "  Files ready for upload:",
            ]
        if self.uploaded_files:
            for f in self.uploaded_files:
                lines.append(f"    - {f}")
        if self.error:
            lines.append(f"  Error: {self.error}")
        lines.append("=" * 60)
        return "\n".join(lines)


# ---------------------------------------------------------------------------
# Manual submission instructions
# ---------------------------------------------------------------------------


def _print_manual_instructions(
    bundle: SubmissionBundle,
    submitter: dict[str, Any],
    model_name: str,
) -> None:
    """Print step-by-step instructions for manual browser submission."""
    files = [bundle.script_path, bundle.weights_path]
    if bundle.requirements_path:
        files.append(bundle.requirements_path)
    if bundle.pdf_path:
        files.append(bundle.pdf_path)

    print()
    print("=" * 70)
    print("MANUAL SUBMISSION REQUIRED")
    print("=" * 70)
    print()
    print("Automated submission requires reCAPTCHA completion in a browser.")
    print(f"Submit your model at: {_CTF_SUBMIT_PAGE}")
    print()
    print("Submitter info (copy into the form):")
    print(f"  First name : {submitter.get('first_name', '')}")
    print(f"  Last name  : {submitter.get('last_name', '')}")
    print(f"  Email      : {submitter.get('email', '')}")
    print(f"  Model name : {model_name}")
    print()
    print("Files to upload:")
    for p in files:
        print(f"  {p}")
    print()
    print("Steps:")
    print("  1. Open the URL above in your browser.")
    print("  2. Fill in your name, email, and model name.")
    print("  3. Upload the files listed above.")
    print("  4. Complete the reCAPTCHA and click Submit.")
    print("=" * 70)
    print()


# ---------------------------------------------------------------------------
# HTTP helpers
# ---------------------------------------------------------------------------


def _post_json(url: str, payload: dict[str, Any], timeout: int) -> tuple[int, dict[str, Any]]:
    """POST JSON payload; return (status_code, response_dict).

    Raises ImportError if requests is not installed.
    """
    try:
        import requests
    except ImportError:
        raise ImportError(
            "requests is required for HTTP submission. "
            "Install it: pip install requests"
        )
    resp = requests.post(
        url,
        json=payload,
        timeout=timeout,
        headers={"Content-Type": "application/json", "User-Agent": "eap-ctf/1.0"},
    )
    try:
        body = resp.json()
    except Exception:
        body = {"raw": resp.text}
    return resp.status_code, body


def _put_file(url: str, path: Path, content_type: str, timeout: int) -> int:
    """PUT file bytes to a presigned URL; return HTTP status code."""
    try:
        import requests
    except ImportError:
        raise ImportError(
            "requests is required for file upload. "
            "Install it: pip install requests"
        )
    with path.open("rb") as fh:
        data = fh.read()
    resp = requests.put(
        url,
        data=data,
        headers={"Content-Type": content_type},
        timeout=timeout,
    )
    return resp.status_code


def _looks_like_captcha(status_code: int, body: dict[str, Any]) -> bool:
    """Heuristic: detect reCAPTCHA / auth blocks."""
    if status_code in (401, 403):
        return True
    text = json.dumps(body).lower()
    return any(kw in text for kw in ("captcha", "recaptcha", "challenge", "forbidden"))


_CONTENT_TYPES: dict[str, str] = {
    ".py": "text/x-python",
    ".csv": "text/csv",
    ".txt": "text/plain",
    ".pdf": "application/pdf",
    ".md": "text/markdown",
}


def _content_type(path: Path) -> str:
    return _CONTENT_TYPES.get(path.suffix.lower(), "application/octet-stream")


# ---------------------------------------------------------------------------
# Main entry point
# ---------------------------------------------------------------------------


def submit(
    bundle: SubmissionBundle,
    submitter: dict[str, Any],
    model_name: str,
    timeout: int = 30,
    open_browser_on_manual: bool = True,
) -> SubmissionResult:
    """Submit a CTF bundle to jkpfactors.com.

    Attempts fully programmatic submission via the presign + register API.
    Falls back to manual instructions if reCAPTCHA blocks automation.

    Parameters
    ----------
    bundle : SubmissionBundle
        Built by ``build_submission()``.  All required paths must exist.
    submitter : dict
        Must contain ``first_name``, ``last_name``, and ``email``.
    model_name : str
        Display name for the submission on the leaderboard.
    timeout : int
        HTTP request timeout in seconds.
    open_browser_on_manual : bool
        If True and manual steps are required, open the submit URL in the
        default browser automatically.

    Returns
    -------
    SubmissionResult
        Always returns (never raises).  Check ``status`` and
        ``manual_steps_required``.
    """
    # --- Validate required submitter fields ---
    for key in ("first_name", "last_name", "email"):
        if key not in submitter or not submitter[key]:
            return SubmissionResult(
                status="error",
                error=f"submitter dict is missing required key: '{key}'",
            )

    # --- Collect bundle files ---
    bundle_files: list[Path] = [bundle.script_path, bundle.weights_path]
    if bundle.requirements_path and bundle.requirements_path.exists():
        bundle_files.append(bundle.requirements_path)
    if bundle.pdf_path and bundle.pdf_path.exists():
        bundle_files.append(bundle.pdf_path)

    for p in bundle_files:
        if not p.exists():
            return SubmissionResult(
                status="error",
                error=f"Bundle file not found: {p}",
            )

    # --- Step 1: Presign ---
    presign_payload = {
        "first_name": submitter["first_name"],
        "last_name": submitter["last_name"],
        "email": submitter["email"],
        "model_name": model_name,
        "files": [{"name": p.name, "content_type": _content_type(p)} for p in bundle_files],
    }

    try:
        status_code, presign_body = _post_json(_CTF_PRESIGN_URL, presign_payload, timeout)
    except ImportError as exc:
        return SubmissionResult(status="error", error=str(exc))
    except Exception as exc:
        # Network error, DNS failure, etc. — fall back to manual
        _print_manual_instructions(bundle, submitter, model_name)
        if open_browser_on_manual:
            webbrowser.open(_CTF_SUBMIT_PAGE)
        return SubmissionResult(
            status="manual_required",
            manual_steps_required=True,
            error=f"Presign request failed: {type(exc).__name__}: {exc}",
        )

    if _looks_like_captcha(status_code, presign_body) or status_code not in (200, 201):
        _print_manual_instructions(bundle, submitter, model_name)
        if open_browser_on_manual:
            webbrowser.open(_CTF_SUBMIT_PAGE)
        return SubmissionResult(
            status="manual_required",
            manual_steps_required=True,
            presign_response=presign_body,
        )

    # --- Step 2: Upload files to S3 ---
    # presign_body is expected to have an "uploads" dict mapping file name → presigned URL
    raw_uploads = presign_body.get("uploads", {})
    uploads: dict[str, Any] = raw_uploads if isinstance(raw_uploads, dict) else {}
    uploaded: list[str] = []

    for p in bundle_files:
        presigned_url = uploads.get(p.name)
        if not presigned_url:
            # No presigned URL for this file — unexpected but non-fatal; warn and skip
            print(f"[WARNING] No presigned URL returned for {p.name}. Skipping upload.")
            continue
        try:
            put_status = _put_file(presigned_url, p, _content_type(p), timeout)
        except Exception as exc:
            return SubmissionResult(
                status="error",
                presign_response=presign_body,
                uploaded_files=uploaded,
                error=f"S3 upload failed for {p.name}: {exc}",
            )
        if put_status not in (200, 204):
            return SubmissionResult(
                status="error",
                presign_response=presign_body,
                uploaded_files=uploaded,
                error=f"S3 upload returned HTTP {put_status} for {p.name}.",
            )
        uploaded.append(str(p))

    # --- Step 3: Register submission ---
    submission_id = presign_body.get("submission_id", "")
    register_payload = {
        "submission_id": submission_id,
        "first_name": submitter["first_name"],
        "last_name": submitter["last_name"],
        "email": submitter["email"],
        "model_name": model_name,
    }

    try:
        reg_status, reg_body = _post_json(_CTF_REGISTER_URL, register_payload, timeout)
    except Exception as exc:
        return SubmissionResult(
            status="error",
            presign_response=presign_body,
            uploaded_files=uploaded,
            error=f"Register request failed: {exc}",
        )

    if _looks_like_captcha(reg_status, reg_body) or reg_status not in (200, 201):
        _print_manual_instructions(bundle, submitter, model_name)
        if open_browser_on_manual:
            webbrowser.open(_CTF_SUBMIT_PAGE)
        return SubmissionResult(
            status="manual_required",
            manual_steps_required=True,
            presign_response=presign_body,
            register_response=reg_body,
            uploaded_files=uploaded,
        )

    return SubmissionResult(
        status="submitted",
        presign_response=presign_body,
        register_response=reg_body,
        uploaded_files=uploaded,
        manual_steps_required=False,
    )
