"""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 hashlib
import logging
import os

# Import uuid
import uuid as _uuid
from datetime import datetime
from typing import Optional

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

# Import get_db from app.database
from app.database import get_db

# Import get_current_user from app.dependencies.auth
from app.dependencies.auth import get_current_user

# Import UnitOfWork from app.domain.unit_of_work
from app.domain.unit_of_work import UnitOfWork

# Import limiter from app.limiter
from app.limiter import limiter

# Import TeamSide from app.models.enums
from app.models.enums import TeamSide

# Import Evidence from app.models.evidence
from app.models.evidence import Evidence

# Import User from app.models.user
from app.models.user import User

# Import EvidenceOut from app.schemas.evidence
from app.schemas.evidence import EvidenceOut

# Import log_action from app.services.audit_service
from app.services.audit_service import log_action

# Import  from app.services.evidence_service
from app.services.evidence_service import (
    MAX_UPLOAD_SIZE,
    get_evidence_or_raise,
    get_test_or_raise,
    list_evidence_for_test,
    validate_delete_permission,
    validate_file,
    validate_upload_permission,
)
from app.storage import download_file, upload_file

logger = logging.getLogger(__name__)

# Assign router = APIRouter(tags=["evidence"])
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(
        # Keyword argument: id
        id=evidence.id,
        # Keyword argument: test_id
        test_id=evidence.test_id,
        # Keyword argument: file_name
        file_name=evidence.file_name,
        # Keyword argument: sha256_hash
        sha256_hash=evidence.sha256_hash,
        # Keyword argument: uploaded_by
        uploaded_by=evidence.uploaded_by,
        # Keyword argument: uploaded_at
        uploaded_at=evidence.uploaded_at,
        # Keyword argument: team
        team=evidence.team,
        # Keyword argument: notes
        notes=evidence.notes,
        download_url=f"/api/v1/evidence/{evidence.id}/file",
    )


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


@router.post(
    # Literal argument value
    "/tests/{test_id}/evidence",
    # Keyword argument: response_model
    response_model=EvidenceOut,
    # Keyword argument: status_code
    status_code=status.HTTP_201_CREATED,
)
# Apply the @limiter.limit decorator
@limiter.limit("10/minute")
# Define async function upload_evidence
async def upload_evidence(
    # Entry: request
    request: Request,
    # Entry: test_id
    test_id: _uuid.UUID,
    # Entry: file
    file: UploadFile = File(...),
    # Entry: team
    team: TeamSide = Form(TeamSide.red),
    # Entry: notes
    notes: Optional[str] = Form(None),
    # Entry: db
    db: Session = Depends(get_db),
    # Entry: current_user
    current_user: User = Depends(get_current_user),
) -> EvidenceOut:
    """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.
    """
    # Assign test = get_test_or_raise(db, test_id)
    test = get_test_or_raise(db, test_id)
    # Call validate_upload_permission()
    validate_upload_permission(test, team, current_user.role)

    # Assign file_name = file.filename or "unnamed"
    file_name = file.filename or "unnamed"
    # Assign content = await file.read(MAX_UPLOAD_SIZE + 1)
    content = await file.read(MAX_UPLOAD_SIZE + 1)
    # Call validate_file()
    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)
    # Assign key = f"{test_id}/{_uuid.uuid4()}_{safe_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:
        # Assign evidence = Evidence(
        evidence = Evidence(
            # Keyword argument: test_id
            test_id=test_id,
            # Keyword argument: file_name
            file_name=safe_name,
            # Keyword argument: file_path
            file_path=key,
            # Keyword argument: sha256_hash
            sha256_hash=sha256,
            # Keyword argument: uploaded_by
            uploaded_by=current_user.id,
            uploaded_at=datetime.utcnow(),  # set explicitly — DB column has no server default
            team=team,
            # Keyword argument: notes
            notes=notes,
        )
        # Stage new record(s) for database insertion
        db.add(evidence)
        # Flush changes to DB without committing the transaction
        db.flush()  # Get evidence.id for audit
        # Call log_action()
        log_action(
            db,
            # Keyword argument: user_id
            user_id=current_user.id,
            # Keyword argument: action
            action="upload_evidence",
            # Keyword argument: entity_type
            entity_type="evidence",
            # Keyword argument: entity_id
            entity_id=evidence.id,
            # Keyword argument: details
            details={
                # Literal argument value
                "file_name": safe_name,
                # Literal argument value
                "sha256": sha256,
                # Literal argument value
                "test_id": str(test_id),
                # Literal argument value
                "team": team.value,
            },
        )
        # Call uow.commit()
        uow.commit()
    # Reload ORM object attributes from the database
    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_admin_jira_client, has_admin_jira_configured
        if not has_admin_jira_configured(db):
            return
        issue_key = get_test_jira_key(db, test_id)
        if not issue_key:
            return
        import io
        jira = get_admin_jira_client(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])
# Define function list_evidence
def list_evidence(
    # Entry: test_id
    test_id: _uuid.UUID,
    # Entry: team
    team: Optional[str] = Query(None, description="Filter by team: red or blue"),
    # Entry: db
    db: Session = Depends(get_db),
    # Entry: current_user
    current_user: User = Depends(get_current_user),
) -> list[EvidenceOut]:
    """List all evidences for a test, optionally filtered by team."""
    # Call get_test_or_raise()
    get_test_or_raise(db, test_id)
    # Assign evidences = list_evidence_for_test(db, test_id, team=team)
    evidences = list_evidence_for_test(db, test_id, team=team)
    # Return [_evidence_to_out(e) for e in evidences]
    return [_evidence_to_out(e) for e in evidences]


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


@router.get("/evidence/{evidence_id}", response_model=EvidenceOut)
# Define function get_evidence
def get_evidence(
    # Entry: evidence_id
    evidence_id: _uuid.UUID,
    # Entry: db
    db: Session = Depends(get_db),
    # Entry: current_user
    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)
    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)
# Define function delete_evidence
def delete_evidence(
    # Entry: evidence_id
    evidence_id: _uuid.UUID,
    # Entry: db
    db: Session = Depends(get_db),
    # Entry: current_user
    current_user: User = Depends(get_current_user),
) -> dict:
    """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``
    """
    # Assign evidence = get_evidence_or_raise(db, evidence_id)
    evidence = get_evidence_or_raise(db, evidence_id)
    # Assign test = get_test_or_raise(db, evidence.test_id)
    test = get_test_or_raise(db, evidence.test_id)
    # Call validate_delete_permission()
    validate_delete_permission(test, evidence, current_user.role, current_user.id)

    # Open context manager
    with UnitOfWork(db) as uow:
        # Call log_action()
        log_action(
            db,
            # Keyword argument: user_id
            user_id=current_user.id,
            # Keyword argument: action
            action="delete_evidence",
            # Keyword argument: entity_type
            entity_type="evidence",
            # Keyword argument: entity_id
            entity_id=evidence.id,
            # Keyword argument: details
            details={
                # Literal argument value
                "file_name": evidence.file_name,
                # Literal argument value
                "test_id": str(evidence.test_id),
                # Literal argument value
                "team": evidence.team.value if evidence.team else None,
            },
        )
        # Mark record for deletion on next commit
        db.delete(evidence)
        # Call uow.commit()
        uow.commit()

    # Return {"detail": "Evidence deleted"}
    return {"detail": "Evidence deleted"}
