"""cortex/api/routes/export_import.py — Export/import endpoints."""

import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from fastapi.responses import JSONResponse

from cortex.api.deps import PREFIX, _serialise, get_plugin
from cortex.api.models import ExportRequest

logger = logging.getLogger(__name__)

router = APIRouter()


def _make_routes(verify_api_key):
    """Register export/import routes with the given auth dependency."""

    @router.get(
        f"{PREFIX}/export/sqlite",
        summary="Export database as SQLite file download",
        tags=["export-import"],
    )
    async def export_sqlite_endpoint(
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ):
        """Download the entire Cortex database as a SQLite file."""
        import tempfile
        from starlette.responses import FileResponse
        from cortex.tools.export_import import export_sqlite

        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail="Storage not available",
            )

        try:
            tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
            tmp.close()
            await export_sqlite(storage, output_path=tmp.name)
            return FileResponse(
                tmp.name,
                media_type="application/x-sqlite3",
                filename="cortex-backup.db",
            )
        except Exception as exc:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail=f"SQLite export failed: {exc}",
            )

    @router.post(
        f"{PREFIX}/export",
        summary="Export memories to JSON, CSV, or Markdown",
        tags=["export-import"],
    )
    async def export_memories(
        body: ExportRequest,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ):
        """Export all memories for an owner in JSON, CSV, or Markdown format.

        Security note (Issue #481): This endpoint is protected by API key auth only.
        In the current single-tenant deployment, API key = full access, so any
        authenticated caller may export any owner's data.  This is an accepted
        design decision for single-tenant use.  Future work: per-owner auth tokens
        will scope exports to the authenticated owner (out of scope for this PR).
        """
        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail="Storage not available",
            )

        owner_id = body.owner_id or "default"

        # Audit log: record who is exporting what. In single-tenant mode the
        # "actor" is the API key identity; expand when per-owner tokens land.
        logger.info(
            "AUDIT export_memories: owner_id=%r format=%r",
            owner_id,
            body.format,
        )

        try:
            result = await storage.browse_memories(
                owner_id=owner_id,
                page=1,
                per_page=10000,
            )
            items = result.get("items", [])
        except Exception as exc:
            raise HTTPException(
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                detail="Internal server error",
            )

        fmt = body.format.lower()

        if fmt == "json":
            return JSONResponse(content={"memories": items, "total": len(items), "owner_id": owner_id})

        elif fmt == "csv":
            import csv
            import io
            from starlette.responses import Response

            output = io.StringIO()
            if items:
                writer = csv.DictWriter(output, fieldnames=items[0].keys())
                writer.writeheader()
                writer.writerows(items)
            return Response(
                content=output.getvalue(),
                media_type="text/csv",
                headers={"Content-Disposition": f"attachment; filename=cortex-export-{owner_id}.csv"},
            )

        elif fmt == "markdown":
            lines = [f"# Cortex Memory Export — {owner_id}\n"]
            for item in items:
                lines.append(f"## {item.get('subject', 'Unknown')}")
                lines.append(f"- **Predicate:** {item.get('predicate', '')}")
                lines.append(f"- **Value:** {item.get('object_value', '')}")
                lines.append(f"- **Category:** {item.get('category', '')}")
                lines.append(f"- **Confidence:** {item.get('confidence', '')}")
                lines.append(f"- **Status:** {item.get('status', '')}")
                lines.append("")
            from starlette.responses import Response
            return Response(
                content="\n".join(lines),
                media_type="text/markdown",
                headers={"Content-Disposition": f"attachment; filename=cortex-export-{owner_id}.md"},
            )
        else:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Unsupported format: {fmt}. Use json, csv, or markdown.",
            )

    @router.post(
        f"{PREFIX}/import",
        summary="Import memories from evaOS JSON or Mem0 JSONL",
        tags=["export-import"],
    )
    async def import_memories(
        format: str = "evaos",
        owner_id: Optional[str] = None,
        dry_run: bool = False,
        on_conflict: str = "skip",
        file: UploadFile = File(...),
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ):
        """Import memories from uploaded file.

        on_conflict: skip (default), overwrite, or merge.
        """
        import json as _json

        storage = getattr(plugin, "storage", None)
        if storage is None:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail="Storage not available",
            )

        owner_id = owner_id or getattr(plugin.config, "owner_id", "default")

        _MAX_IMPORT_BYTES = 10_000_000  # 10 MB

        try:
            # Read one byte beyond the limit to detect oversized uploads (R-HIGH-2)
            contents = await file.read(_MAX_IMPORT_BYTES + 1)
            if len(contents) > _MAX_IMPORT_BYTES:
                raise HTTPException(
                    status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
                    detail="Import file too large (max 10MB)",
                )
            text = contents.decode("utf-8")
        except HTTPException:
            raise
        except Exception as exc:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Internal server error",
            )

        imported = 0
        skipped = 0
        errors: List[str] = []

        if format == "evaos":
            try:
                data = _json.loads(text)
                memories = data if isinstance(data, list) else data.get("memories", [])
            except Exception as exc:
                raise HTTPException(
                    status_code=status.HTTP_400_BAD_REQUEST,
                    detail=f"Invalid JSON: {exc}",
                )

            for idx, mem in enumerate(memories):
                # Issue #489 — validate required claim fields before attempting write
                _has_object = (
                    mem.get("object_")
                    or mem.get("object_value")
                    or mem.get("content")
                )
                if not mem.get("subject") or not mem.get("predicate") or not _has_object:
                    logger.warning(
                        "import_memories: skipping malformed claim at index %d "
                        "(missing subject/predicate/object): %r",
                        idx,
                        {k: mem.get(k) for k in ("subject", "predicate", "object_", "object_value")},
                    )
                    skipped += 1
                    errors.append(f"claim[{idx}]: missing required field(s) — subject/predicate/object_")
                    continue

                if dry_run:
                    imported += 1
                    continue
                try:
                    await storage.create_claim(
                        subject=mem.get("subject", "unknown"),
                        predicate=mem.get("predicate", "has"),
                        object_value=mem.get("object_value", mem.get("object_", mem.get("content", ""))),
                        confidence=mem.get("confidence", 0.7),
                        owner_id=owner_id,
                        session_id=mem.get("session_id", "import"),
                    )
                    imported += 1
                except Exception as exc:
                    skipped += 1
                    errors.append(str(exc))

        elif format == "mem0":
            for line in text.strip().split("\n"):
                if not line.strip():
                    continue
                try:
                    mem = _json.loads(line)
                except Exception:
                    skipped += 1
                    continue
                if dry_run:
                    imported += 1
                    continue
                try:
                    await storage.create_claim(
                        subject=mem.get("user_id", "unknown"),
                        predicate="remembers",
                        object_value=mem.get("memory", mem.get("text", "")),
                        confidence=0.7,
                        owner_id=owner_id,
                        session_id="mem0-import",
                    )
                    imported += 1
                except Exception as exc:
                    skipped += 1
                    errors.append(str(exc))
        else:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Unsupported format: {format}. Use evaos or mem0.",
            )

        # Audit log for import (Issue #481 — consistent audit trail)
        logger.info(
            "AUDIT import_memories: owner_id=%r format=%r imported=%d skipped=%d dry_run=%s",
            owner_id,
            format,
            imported,
            skipped,
            dry_run,
        )

        return JSONResponse(content={
            "imported": imported,
            "skipped": skipped,
            "skipped_malformed": len([e for e in errors if "missing required field" in e]),
            "dry_run": dry_run,
            "errors": errors[:10],
        })
