import os
import unittest
from unittest.mock import patch

from botocore.exceptions import ClientError
from fastapi.testclient import TestClient
from sqlalchemy import create_engine, delete
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool

os.environ.setdefault("DATABASE_URL", "sqlite+pysqlite:///:memory:")
os.environ.setdefault("REDIS_URL", "redis://localhost:6379/0")
os.environ.setdefault("S3_ENDPOINT", "http://localhost:9000")
os.environ.setdefault("S3_ACCESS_KEY", "test")
os.environ.setdefault("S3_SECRET_KEY", "test")
os.environ.setdefault("S3_BUCKET", "test")

from app.db.session import get_db
from app.main import app
from app.models.admin_user import AdminUser
from app.models.landing_featured_staff import LandingFeaturedStaff
from app.models.topic import Topic


class _FakeBody:
    def __init__(self, payload: bytes):
        self.payload = payload

    def iter_chunks(self, chunk_size=65536):
        for i in range(0, len(self.payload), chunk_size):
            yield self.payload[i : i + chunk_size]


class _FakeS3Storage:
    def __init__(self):
        self.objects = {}

    def get_object(self, key: str) -> dict:
        obj = self.objects.get(key)
        if obj is None:
            raise ClientError({"Error": {"Code": "404", "Message": "Not Found"}}, "GetObject")
        return {"Body": _FakeBody(obj["content"]), "ContentType": obj["mime"], "ContentLength": obj["size"]}


class FeaturedStaffPublicTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.engine = create_engine(
            "sqlite+pysqlite:///:memory:",
            connect_args={"check_same_thread": False},
            poolclass=StaticPool,
        )
        cls.SessionLocal = sessionmaker(bind=cls.engine, autocommit=False, autoflush=False)
        AdminUser.__table__.create(bind=cls.engine)
        Topic.__table__.create(bind=cls.engine)
        LandingFeaturedStaff.__table__.create(bind=cls.engine)

    @classmethod
    def tearDownClass(cls):
        LandingFeaturedStaff.__table__.drop(bind=cls.engine)
        Topic.__table__.drop(bind=cls.engine)
        AdminUser.__table__.drop(bind=cls.engine)
        cls.engine.dispose()

    def setUp(self):
        with self.SessionLocal() as db:
            db.execute(delete(LandingFeaturedStaff))
            db.execute(delete(Topic))
            db.execute(delete(AdminUser))
            db.commit()

        def override_get_db():
            db = self.SessionLocal()
            try:
                yield db
            finally:
                db.close()

        app.dependency_overrides[get_db] = override_get_db
        self.client = TestClient(app)

    def tearDown(self):
        self.client.close()
        app.dependency_overrides.clear()

    def _seed_featured_lawyer(self, enabled: bool = True):
        with self.SessionLocal() as db:
            topic = Topic(code="consulting", name="consulting", enabled=True, sort_order=1)
            user = AdminUser(
                role="LAWYER",
                name="Юрист",
                email="lawyer-featured@example.com",
                password_hash="hash",
                is_active=True,
                primary_topic_code="consulting",
            )
            db.add_all([topic, user])
            db.flush()
            avatar_key = f"avatars/{user.id}/avatar.webp"
            user.avatar_url = "s3://" + avatar_key
            slot = LandingFeaturedStaff(
                admin_user_id=user.id,
                caption="Я крут!",
                sort_order=10,
                pinned=True,
                enabled=enabled,
            )
            db.add(slot)
            db.commit()
            return str(user.id), avatar_key

    def test_list_featured_staff_returns_public_avatar_proxy_url(self):
        user_id, _ = self._seed_featured_lawyer(enabled=True)

        response = self.client.get("/api/public/featured-staff?limit=24")
        self.assertEqual(response.status_code, 200)
        payload = response.json()
        self.assertEqual(payload.get("total"), 1)
        self.assertEqual(len(payload.get("items") or []), 1)
        row = payload["items"][0]
        self.assertEqual(row.get("admin_user_id"), user_id)
        self.assertEqual(row.get("avatar_url"), "/api/public/featured-staff/avatar/" + user_id + "?variant=thumb")

    def test_featured_staff_avatar_proxy_streams_s3_avatar(self):
        user_id, avatar_key = self._seed_featured_lawyer(enabled=True)
        fake_s3 = _FakeS3Storage()
        fake_s3.objects[avatar_key] = {
            "size": 7,
            "mime": "image/webp",
            "content": b"webpimg",
        }

        with patch("app.api.public.featured_staff.get_s3_storage", return_value=fake_s3):
            response = self.client.get("/api/public/featured-staff/avatar/" + user_id)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"webpimg")
        self.assertIn("image/webp", response.headers.get("content-type", ""))

    def test_featured_staff_avatar_proxy_denies_non_featured_user(self):
        user_id, avatar_key = self._seed_featured_lawyer(enabled=False)
        fake_s3 = _FakeS3Storage()
        fake_s3.objects[avatar_key] = {
            "size": 7,
            "mime": "image/webp",
            "content": b"webpimg",
        }

        with patch("app.api.public.featured_staff.get_s3_storage", return_value=fake_s3):
            response = self.client.get("/api/public/featured-staff/avatar/" + user_id)

        self.assertEqual(response.status_code, 404)
