import io
from typing import Any, cast
from unittest.mock import Mock

import pytest
from fastapi import HTTPException, status
from pydantic import ValidationError
from src.routers.management.users_batch_router import (
    BatchCreateUsersRequest,
    _parse_csv,
    _read_csv_rows,
    _validate_users,
)


def _make_upload_file(content: str, filename: str = "test.csv") -> Mock:
    """テスト用のUploadFile相当オブジェクトを作成する。"""
    file_mock = Mock()
    file_mock.file = io.BytesIO(content.encode("utf-8-sig"))
    file_mock.filename = filename
    return file_mock


class TestReadCsvRows:
    def test_parses_valid_csv(self) -> None:
        """正常なCSVをパースし、フィールドマッピングが適用されることを確認する。"""
        csv_content = "ID,名称,メールアドレス\n0001,ユーザー1,user1@example.com\n"
        file = _make_upload_file(csv_content)

        rows = _read_csv_rows(file)

        assert rows == [{"display_id": "0001", "display_name": "ユーザー1", "email": "user1@example.com"}]

    def test_parses_multiple_rows(self) -> None:
        """複数行のCSVを正しくパースできることを確認する。"""
        csv_content = "ID,名称,メールアドレス\n0001,ユーザー1,user1@example.com\n0002,ユーザー2,user2@example.com\n"
        file = _make_upload_file(csv_content)

        rows = _read_csv_rows(file)

        assert len(rows) == 2
        assert rows[1] == {"display_id": "0002", "display_name": "ユーザー2", "email": "user2@example.com"}

    def test_rejects_oversized_file(self) -> None:
        """ファイルサイズ上限超過時に413エラーを返す。"""
        csv_content = "ID,名称,メールアドレス\n" + "x" * (1024 * 1024 + 1)
        file = _make_upload_file(csv_content)

        with pytest.raises(HTTPException) as exc_info:
            _read_csv_rows(file)

        assert exc_info.value.status_code == status.HTTP_413_CONTENT_TOO_LARGE

    def test_rejects_invalid_encoding(self) -> None:
        """UTF-8でデコードできないファイルは422エラーを返す。"""
        file = Mock()
        file.file = io.BytesIO(b"\xff\xfe" + b"\x80\x81\x82" * 100)
        file.filename = "test.csv"

        with pytest.raises(HTTPException) as exc_info:
            _read_csv_rows(file)

        assert exc_info.value.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT

    def test_rejects_wrong_headers(self) -> None:
        """期待と異なるヘッダーのCSVは422エラーを返す。"""
        csv_content = "id,name,email\n0001,ユーザー1,user1@example.com\n"
        file = _make_upload_file(csv_content)

        with pytest.raises(HTTPException) as exc_info:
            _read_csv_rows(file)

        assert exc_info.value.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT

    def test_rejects_missing_headers(self) -> None:
        """ヘッダーが不足しているCSVは422エラーを返す。"""
        csv_content = "ID,名称\n0001,ユーザー1\n"
        file = _make_upload_file(csv_content)

        with pytest.raises(HTTPException) as exc_info:
            _read_csv_rows(file)

        assert exc_info.value.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT


class TestValidateUsers:
    def test_valid_users(self) -> None:
        """重複のないユーザーリストはバリデーションを通過する。"""
        rows = [
            {"display_id": "0001", "display_name": "ユーザー1", "email": "user1@example.com"},
            {"display_id": "0002", "display_name": "ユーザー2", "email": "user2@example.com"},
        ]

        result = _validate_users(rows)

        assert isinstance(result, BatchCreateUsersRequest)
        assert len(result.users) == 2

    def test_rejects_duplicate_display_id(self) -> None:
        """バッチ内で display_id が重複する場合、422エラーを返す。"""
        rows = [
            {"display_id": "0001", "display_name": "ユーザー1", "email": "user1@example.com"},
            {"display_id": "0001", "display_name": "ユーザー2", "email": "user2@example.com"},
        ]

        with pytest.raises(HTTPException) as exc_info:
            _validate_users(rows)

        detail = cast("dict[str, Any]", exc_info.value.detail)  # detail が str でないことを明示的に示すためにキャスト
        assert exc_info.value.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
        assert any(p["name"] == "users[1].display_id" for p in detail["invalid_parameters"])

    def test_rejects_duplicate_email(self) -> None:
        """バッチ内で email が重複する場合、422エラーを返す。"""
        rows = [
            {"display_id": "0001", "display_name": "ユーザー1", "email": "same@example.com"},
            {"display_id": "0002", "display_name": "ユーザー2", "email": "same@example.com"},
        ]

        with pytest.raises(HTTPException) as exc_info:
            _validate_users(rows)

        detail = cast("dict[str, Any]", exc_info.value.detail)  # detail が str でないことを明示的に示すためにキャスト
        assert exc_info.value.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
        assert any(p["name"] == "users[1].email" for p in detail["invalid_parameters"])

    def test_rejects_empty_rows(self) -> None:
        """空のリストはバリデーションエラーになる。"""
        with pytest.raises(ValidationError):
            _validate_users([])

    def test_rejects_over_max_rows(self) -> None:
        """上限超過のリストはバリデーションエラーになる。"""
        rows = [
            {"display_id": f"{i:04d}", "display_name": f"ユーザー{i}", "email": f"user{i}@example.com"}
            for i in range(101)
        ]

        with pytest.raises(ValidationError):
            _validate_users(rows)


class TestParseCsv:
    def test_end_to_end_valid(self) -> None:
        """正常なCSVファイルからBatchCreateUsersRequestが生成される。"""
        csv_content = "ID,名称,メールアドレス\n0001,ユーザー1,user1@example.com\n0002,ユーザー2,user2@example.com\n"
        file = _make_upload_file(csv_content)

        result = _parse_csv(file)

        assert isinstance(result, BatchCreateUsersRequest)
        assert len(result.users) == 2
        assert result.users[0].display_id == "0001"
        assert result.users[0].email == "user1@example.com"
