"""이미지 검증 모듈 및 API 테스트."""

import json
import shutil
import struct
import uuid
import zlib
from datetime import datetime, timezone
from pathlib import Path

import pytest
from fastapi.testclient import TestClient

from app.core.config import settings
from app.core.database import Base, engine, SessionLocal
from app.main import app
from app.models.project import ImageAsset
from app.modules.image_validator import ImageValidator, VALIDATION_SCHEMA
from tests._safety_guards import safe_drop_all, safe_rmtree


def _make_minimal_png() -> bytes:
    """Create a minimal valid 1x1 white PNG."""
    signature = b'\x89PNG\r\n\x1a\n'

    def chunk(chunk_type, data):
        c = chunk_type + data
        crc = struct.pack('>I', zlib.crc32(c) & 0xffffffff)
        return struct.pack('>I', len(data)) + c + crc

    ihdr_data = struct.pack('>IIBBBBB', 1, 1, 8, 2, 0, 0, 0)
    raw_data = b'\x00\xff\xff\xff'
    idat_data = zlib.compress(raw_data)

    return signature + chunk(b'IHDR', ihdr_data) + chunk(b'IDAT', idat_data) + chunk(b'IEND', b'')


@pytest.fixture(autouse=True)
def _setup_db():
    Base.metadata.create_all(engine)
    with TestClient(app):
        pass
    yield
    safe_drop_all(engine, Base.metadata)
    proj_dir = Path(settings.projects_dir)
    if proj_dir.exists():
        safe_rmtree(proj_dir)


@pytest.fixture()
def client():
    with TestClient(app, raise_server_exceptions=False) as c:
        yield c


def _admin_login(client: TestClient):
    resp = client.post("/api/v1/auth/login", json={"username": "admin", "password": "admin123"})
    assert resp.status_code == 200


def _create_project(client: TestClient) -> str:
    _admin_login(client)
    resp = client.post("/api/v1/projects/", json={"name": "Validator Test Proj"})
    assert resp.status_code == 200
    return resp.json()["id"]


def _insert_image_asset(project_id: str, file_path: str = "test_image.png", **overrides) -> str:
    """Directly insert an ImageAsset into the DB for testing."""
    db = SessionLocal()
    try:
        image_id = overrides.pop("id", str(uuid.uuid4()))
        now = datetime.now(timezone.utc).isoformat()
        defaults = {
            "id": image_id,
            "project_id": project_id,
            "asset_type": "reference",
            "entity_id": None,
            "still_id": None,
            "episode_id": None,
            "file_path": file_path,
            "prompt_used": "test prompt",
            "generation_model": "test-model",
            "width": 800,
            "height": 600,
            "status": "generated",
            "review_notes": "",
            "validation_score": None,
            "validation_result": None,
            "created_at": now,
        }
        defaults.update(overrides)
        asset = ImageAsset(**defaults)
        db.add(asset)
        db.commit()
        return defaults["id"]
    finally:
        db.close()


# ------------------------------------------------------------------
# Test: Validation schema structure
# ------------------------------------------------------------------

def test_validation_schema_has_required_fields():
    schema = VALIDATION_SCHEMA
    assert schema["type"] == "object"
    required = schema["required"]
    assert "score" in required
    assert "passed" in required
    assert "issues" in required
    assert "description" in required
    props = schema["properties"]
    assert props["score"]["type"] == "integer"
    assert props["passed"]["type"] == "boolean"
    assert props["issues"]["type"] == "array"
    assert props["description"]["type"] == "string"


# ------------------------------------------------------------------
# Test: Validate endpoint returns error when no API key
# ------------------------------------------------------------------

def test_validate_endpoint_no_openai_key(client: TestClient):
    # DB 는 상대 경로 (alembic 005 ck_image_asset_file_path_relative). 파일은
    # settings.projects_dir.parent / rel 아래 생성 — production resolve_image_path 와 일치.
    rel = "test_validate.png"
    png_path = Path(settings.projects_dir).parent / rel
    png_path.parent.mkdir(parents=True, exist_ok=True)
    png_path.write_bytes(_make_minimal_png())

    project_id = _create_project(client)
    image_id = _insert_image_asset(project_id, file_path=rel)

    # [2026-08-01] 키 유무의 권위가 슬롯 브로커로 옮겼다 — 1차 키만 비우면
    # 보조 슬롯·환경변수가 살아 있어 "있음"이 되고, 400 대신 500 으로 샌다.
    import os as _os

    original_key = settings.openai_api_key
    original_secondary = settings.openai_api_key_secondary
    original_env = _os.environ.pop("OPENAI_API_KEY", None)
    settings.openai_api_key = ""
    settings.openai_api_key_secondary = ""
    try:
        resp = client.post(
            f"/api/v1/projects/{project_id}/images/{image_id}/validate"
        )
        assert resp.status_code == 400
        assert resp.json()["error"]["code"] == "image.openai_key_missing"
    finally:
        settings.openai_api_key = original_key
        settings.openai_api_key_secondary = original_secondary
        if original_env is not None:
            _os.environ["OPENAI_API_KEY"] = original_env


# ------------------------------------------------------------------
# Test: Get validation result for image without validation
# ------------------------------------------------------------------

def test_get_validation_no_result(client: TestClient):
    project_id = _create_project(client)
    image_id = _insert_image_asset(project_id)

    resp = client.get(
        f"/api/v1/projects/{project_id}/images/{image_id}/validation"
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["image_id"] == image_id
    assert data["score"] is None
    assert data["passed"] is None
    assert data["issues"] == []


# ------------------------------------------------------------------
# Test: Get validation result for image with existing validation
# ------------------------------------------------------------------

def test_get_validation_with_result(client: TestClient):
    project_id = _create_project(client)
    validation_data = {
        "score": 85,
        "passed": True,
        "issues": [],
        "description": "Image matches the description well.",
    }
    image_id = _insert_image_asset(
        project_id,
        validation_score=85,
        validation_result=json.dumps(validation_data),
    )

    resp = client.get(
        f"/api/v1/projects/{project_id}/images/{image_id}/validation"
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["image_id"] == image_id
    assert data["score"] == 85
    assert data["passed"] is True
    assert data["issues"] == []
    assert data["description"] == "Image matches the description well."


# ------------------------------------------------------------------
# Test: Validate endpoint returns 404 for nonexistent image
# ------------------------------------------------------------------

def test_validate_endpoint_image_not_found(client: TestClient):
    project_id = _create_project(client)

    original_key = settings.openai_api_key
    settings.openai_api_key = "test-key"
    try:
        resp = client.post(
            f"/api/v1/projects/{project_id}/images/nonexistent-id/validate"
        )
        assert resp.status_code == 404
    finally:
        settings.openai_api_key = original_key


# ------------------------------------------------------------------
# Test: ImageResponse schema includes validation fields
# ------------------------------------------------------------------

def test_image_response_includes_validation_fields(client: TestClient):
    project_id = _create_project(client)
    image_id = _insert_image_asset(
        project_id,
        validation_score=72,
        validation_result='{"score":72,"passed":true,"issues":[],"description":"ok"}',
    )

    resp = client.get(f"/api/v1/projects/{project_id}/images/{image_id}")
    assert resp.status_code == 200
    data = resp.json()
    assert data["validation_score"] == 72
    assert data["validation_result"] is not None
    parsed = json.loads(data["validation_result"])
    assert parsed["score"] == 72
    assert parsed["passed"] is True
