"""Evidence upload, download, listing and deletion router — v2 with Red/Blue separation.

Endpoints
---------
POST   /tests/{test_id}/evidence       — upload evidence (with team=red/blue)
GET    /tests/{test_id}/evidence       — list evidences (filterable by team)
GET    /evidence/{id}                  — metadata + download_url
GET    /evidence/{id}/file             — proxy download (streams file through backend)
DELETE /evidence/{id}                  — delete evidence (only in editable states)

Access Control
--------------
- Red Team (``red_tech``) can only upload ``team=red`` when test is in
  ``draft`` or ``red_executing``.
- Blue Team (``blue_tech``) can only upload ``team=blue`` when test is in
  ``blue_evaluating``.
- Admin can upload any team in any state.
- DELETE is restricted: red evidence in ``draft``/``red_executing``,
  blue evidence in ``blue_evaluating``.  No deletions in ``in_review``,
  ``validated``, or ``rejected``.
"""

import hashlib
import logging
import os
import uuid as _uuid
from datetime import datetime
from typing import Optional

from fastapi import APIRouter, Depends, File, Form, Query, Request, UploadFile, status
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session

from app.database import get_db
from app.domain.unit_of_work import UnitOfWork
from app.dependencies.auth import get_current_user
from app.models.enums import TeamSide
from app.models.evidence import Evidence
from app.models.user import User
from app.schemas.evidence import EvidenceOut
from app.services.audit_service import log_action
from app.services.evidence_service import (
    get_evidence_or_raise,
    get_test_or_raise,
    list_evidence_for_test,
    MAX_UPLOAD_SIZE,
    validate_delete_permission,
    validate_file,
    validate_upload_permission,
)
from app.limiter import limiter
from app.storage import download_file, upload_file

logger = logging.getLogger(__name__)

router = APIRouter(tags=["evidence"])


# ---------------------------------------------------------------------------
# Helpers (router-specific: infrastructure / HTTP concerns)
# ---------------------------------------------------------------------------

def _evidence_to_out(evidence: Evidence) -> EvidenceOut:
    """Convert an ORM ``Evidence`` to the API schema.

    ``download_url`` points to the backend proxy endpoint so the browser
    never needs direct access to MinIO.
    """
    return EvidenceOut(
        id=evidence.id,
        test_id=evidence.test_id,
        file_name=evidence.file_name,
        sha256_hash=evidence.sha256_hash,
        uploaded_by=evidence.uploaded_by,
        uploaded_at=evidence.uploaded_at,
        team=evidence.team,
        notes=evidence.notes,
        download_url=f"/api/v1/evidence/{evidence.id}/file",
    )


# ---------------------------------------------------------------------------
# POST /tests/{test_id}/evidence  —  upload with team
# ---------------------------------------------------------------------------


@router.post(
    "/tests/{test_id}/evidence",
    response_model=EvidenceOut,
    status_code=status.HTTP_201_CREATED,
)
@limiter.limit("10/minute")
async def upload_evidence(
    request: Request,
    test_id: _uuid.UUID,
    file: UploadFile = File(...),
    team: TeamSide = Form(TeamSide.red),
    notes: Optional[str] = Form(None),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Upload a file as evidence for the given test.

    The ``team`` field (sent as form data) determines whether this is
    Red Team (attack) or Blue Team (detection) evidence.
    """
    test = get_test_or_raise(db, test_id)
    validate_upload_permission(test, team, current_user.role)

    file_name = file.filename or "unnamed"
    content = await file.read(MAX_UPLOAD_SIZE + 1)
    validate_file(file_name, len(content))

    # Hash
    sha256 = hashlib.sha256(content).hexdigest()

    # 4. Object key (sanitise filename to prevent path traversal in storage)
    safe_name = os.path.basename(file_name)
    key = f"{test_id}/{_uuid.uuid4()}_{safe_name}"

    # 5. Upload to MinIO
    upload_file(content, key)

    # 6. Persist metadata and audit
    with UnitOfWork(db) as uow:
        evidence = Evidence(
            test_id=test_id,
            file_name=safe_name,
            file_path=key,
            sha256_hash=sha256,
            uploaded_by=current_user.id,
            uploaded_at=datetime.utcnow(),  # set explicitly — DB column has no server default
            team=team,
            notes=notes,
        )
        db.add(evidence)
        db.flush()  # Get evidence.id for audit
        log_action(
            db,
            user_id=current_user.id,
            action="upload_evidence",
            entity_type="evidence",
            entity_id=evidence.id,
            details={
                "file_name": safe_name,
                "sha256": sha256,
                "test_id": str(test_id),
                "team": team.value,
            },
        )
        uow.commit()
    db.refresh(evidence)

    # 7. Attach to Jira ticket if one exists (non-fatal)
    _attach_evidence_to_jira(db, test_id, content, safe_name, current_user)

    return _evidence_to_out(evidence)


def _attach_evidence_to_jira(
    db,
    test_id: _uuid.UUID,
    content: bytes,
    file_name: str,
    actor,
) -> None:
    """Attach uploaded evidence to the linked Jira ticket (non-fatal)."""
    try:
        from app.services.jira_service import get_test_jira_key, get_user_jira_client, has_jira_configured
        if not has_jira_configured(actor, db):
            return
        issue_key = get_test_jira_key(db, test_id)
        if not issue_key:
            return
        import io
        jira = get_user_jira_client(actor, db)
        buf = io.BytesIO(content)
        buf.name = file_name  # requests uses .name as the multipart filename
        jira.add_attachment_object(issue_key, buf)
        import logging
        logging.getLogger(__name__).info(
            "Attached evidence '%s' to Jira ticket %s", file_name, issue_key
        )
    except Exception as exc:
        import logging
        logging.getLogger(__name__).warning(
            "Failed to attach evidence '%s' to Jira: %s", file_name, exc, exc_info=True
        )


# ---------------------------------------------------------------------------
# GET /tests/{test_id}/evidence  —  list (with optional team filter)
# ---------------------------------------------------------------------------


@router.get("/tests/{test_id}/evidence", response_model=list[EvidenceOut])
def list_evidence(
    test_id: _uuid.UUID,
    team: Optional[str] = Query(None, description="Filter by team: red or blue"),
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """List all evidences for a test, optionally filtered by team."""
    get_test_or_raise(db, test_id)
    evidences = list_evidence_for_test(db, test_id, team=team)
    return [_evidence_to_out(e) for e in evidences]


# ---------------------------------------------------------------------------
# GET /evidence/{id}  —  metadata + proxy download URL
# ---------------------------------------------------------------------------


@router.get("/evidence/{evidence_id}", response_model=EvidenceOut)
def get_evidence(
    evidence_id: _uuid.UUID,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Return evidence metadata. ``download_url`` is a backend proxy URL."""
    evidence = get_evidence_or_raise(db, evidence_id)
    return _evidence_to_out(evidence)


# ---------------------------------------------------------------------------
# GET /evidence/{id}/file  —  proxy download (streams file via backend)
# ---------------------------------------------------------------------------


@router.get("/evidence/{evidence_id}/file")
def download_evidence_file(
    evidence_id: _uuid.UUID,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Stream the evidence file through the backend.

    The browser calls this endpoint (authenticated via JWT cookie/header).
    The backend fetches the file from MinIO internally and streams it back,
    so MinIO never needs to be publicly accessible.
    """
    import mimetypes

    evidence = get_evidence_or_raise(db, evidence_id)
    content = download_file(evidence.file_path)

    mime_type, _ = mimetypes.guess_type(evidence.file_name)
    if not mime_type:
        mime_type = "application/octet-stream"

    safe_name = evidence.file_name.replace('"', '\\"')
    return StreamingResponse(
        iter([content]),
        media_type=mime_type,
        headers={
            "Content-Disposition": f'inline; filename="{safe_name}"',
            "Content-Length": str(len(content)),
        },
    )


# ---------------------------------------------------------------------------
# DELETE /evidence/{id}  —  delete evidence (editable states only)
# ---------------------------------------------------------------------------


@router.delete("/evidence/{evidence_id}", status_code=status.HTTP_200_OK)
def delete_evidence(
    evidence_id: _uuid.UUID,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """Delete an evidence record.

    Only allowed in editable states:
    - Red evidence: ``draft``, ``red_executing``
    - Blue evidence: ``blue_evaluating``
    - No deletions in ``in_review``, ``validated``, ``rejected``
    """
    evidence = get_evidence_or_raise(db, evidence_id)
    test = get_test_or_raise(db, evidence.test_id)
    validate_delete_permission(test, evidence, current_user.role, current_user.id)

    with UnitOfWork(db) as uow:
        log_action(
            db,
            user_id=current_user.id,
            action="delete_evidence",
            entity_type="evidence",
            entity_id=evidence.id,
            details={
                "file_name": evidence.file_name,
                "test_id": str(evidence.test_id),
                "team": evidence.team.value if evidence.team else None,
            },
        )
        db.delete(evidence)
        uow.commit()

    return {"detail": "Evidence deleted"}
