import io
import uuid

import pytest
from fastapi import status
from fastapi.testclient import TestClient


@pytest.mark.usefixtures("mock_cognito_client")
class TestUsersImportValidatePost:
    PATH = "/v1/users/import/validate"

    def _make_csv(self, rows: list[list[str]]) -> io.BytesIO:
        """CSVデータを生成するヘルパー。rowsの先頭行はヘッダー。"""
        lines = [",".join(row) for row in rows]
        content = "\n".join(lines).encode("utf-8-sig")
        return io.BytesIO(content)

    def test_valid_csv(self, test_client: TestClient) -> None:
        """重複のない有効なCSVでバリデーション成功を確認する。"""
        display_id = f"it-validate-{uuid.uuid4().hex[:8]}"
        csv_bytes = self._make_csv(
            [
                ["ID", "名称", "メールアドレス"],
                [display_id, "テストユーザー", f"{display_id}@example.com"],
            ],
        )

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_204_NO_CONTENT
        assert response.content == b""

    def test_file_size_exceeds_limit(self, test_client: TestClient) -> None:
        """1MBを超えるファイルが413で拒否されることを確認する。"""
        header = "ID,名称,メールアドレス\n"
        padding_row = "x" * 200 + ",name,a@b.com\n"
        repeat_count = (1 * 1024 * 1024) // len(padding_row.encode()) + 1
        content = (header + padding_row * repeat_count).encode("utf-8-sig")
        csv_bytes = io.BytesIO(content)

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_413_CONTENT_TOO_LARGE

    @pytest.mark.parametrize(
        "encoding",
        [
            "utf-8",  # BOMなしUTF-8
            "utf-8-sig",  # BOM付きUTF-8
            "shift_jis",  # Shift-JIS
            "cp932",  # CP932
        ],
    )
    def test_valid_encoding(self, test_client: TestClient, encoding: str) -> None:
        """対応エンコーディングでバリデーション成功を確認する。"""
        display_id = f"it-enc-{uuid.uuid4().hex[:8]}"
        content = f"ID,名称,メールアドレス\n{display_id},テストユーザー,{display_id}@example.com".encode(encoding)
        csv_bytes = io.BytesIO(content)

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_204_NO_CONTENT

    def test_unsupported_encoding(self, test_client: TestClient) -> None:
        """対応外エンコーディングが422で拒否されることを確認する。"""
        content = "ID,名称,メールアドレス\nuser1,ユーザー,a@b.com".encode("utf-16")
        csv_bytes = io.BytesIO(content)

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT

    def test_invalid_headers(self, test_client: TestClient) -> None:
        """期待と異なるCSVヘッダーが422で拒否されることを確認する。"""
        csv_bytes = self._make_csv(
            [
                ["id", "name", "email"],
                ["user1", "テスト", "a@b.com"],
            ],
        )

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
        assert response.json()["invalid_parameters"] == [
            {"name": "file.headers", "code": "INVALID_FORMAT"},
        ]

    def test_duplicate_display_id_in_csv(self, test_client: TestClient) -> None:
        """CSV内でdisplay_idが重複している場合に422で拒否されることを確認する。"""
        display_id = f"it-dup-{uuid.uuid4().hex[:8]}"
        csv_bytes = self._make_csv(
            [
                ["ID", "名称", "メールアドレス"],
                [display_id, "ユーザー1", f"{display_id}_1@example.com"],
                [display_id, "ユーザー2", f"{display_id}_2@example.com"],
            ],
        )

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
        assert response.json() == {
            "detail": "Validation failed for one or more fields.",
            "invalid_parameters": [
                {"name": "users[1].display_id", "code": "ALREADY_EXISTS"},
            ],
        }

    def test_duplicate_email_in_csv(self, test_client: TestClient) -> None:
        """CSV内でemailが重複している場合に422で拒否されることを確認する。"""
        email = f"it-dup-{uuid.uuid4().hex[:8]}@example.com"
        csv_bytes = self._make_csv(
            [
                ["ID", "名称", "メールアドレス"],
                [f"user-{uuid.uuid4().hex[:8]}", "ユーザー1", email],
                [f"user-{uuid.uuid4().hex[:8]}", "ユーザー2", email],
            ],
        )

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
        assert response.json() == {
            "detail": "Validation failed for one or more fields.",
            "invalid_parameters": [
                {"name": "users[1].email", "code": "ALREADY_EXISTS"},
            ],
        }

    def test_invalid_email_format(self, test_client: TestClient) -> None:
        """不正なemail形式が422で拒否されることを確認する。"""
        csv_bytes = self._make_csv(
            [
                ["ID", "名称", "メールアドレス"],
                [f"it-{uuid.uuid4().hex[:8]}", "テスト", "invalid-email"],
            ],
        )

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT

    def test_exceeds_max_rows(self, test_client: TestClient) -> None:
        """101件以上のデータ行が422で拒否されることを確認する。"""
        rows: list[list[str]] = [["ID", "名称", "メールアドレス"]]
        rows.extend([f"it-over-{i:04d}", f"ユーザー{i}", f"it-over-{i:04d}@example.com"] for i in range(101))
        csv_bytes = self._make_csv(rows)

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT

    def test_empty_rows(self, test_client: TestClient) -> None:
        """ヘッダーのみでデータ行がない場合に422で拒否されることを確認する。"""
        csv_bytes = self._make_csv(
            [
                ["ID", "名称", "メールアドレス"],
            ],
        )

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT

    def test_display_id_conflicts_with_existing(self, test_client: TestClient) -> None:
        """既存ユーザーのdisplay_idと重複する場合に409で拒否されることを確認する。"""
        csv_bytes = self._make_csv(
            [
                ["ID", "名称", "メールアドレス"],
                ["0001", "重複テスト", f"it-conflict-{uuid.uuid4().hex[:8]}@example.com"],
            ],
        )

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_409_CONFLICT
        assert response.json() == {
            "detail": "Conflict detected for one or more fields.",
            "invalid_parameters": [
                {"name": "users[0].display_id", "code": "ALREADY_EXISTS"},
            ],
        }

    def test_email_conflicts_with_existing(self, test_client: TestClient) -> None:
        """既存ユーザーのemailと重複する場合に409で拒否されることを確認する。"""
        csv_bytes = self._make_csv(
            [
                ["ID", "名称", "メールアドレス"],
                [f"it-conflict-{uuid.uuid4().hex[:8]}", "重複テスト", "test001@test.com"],
            ],
        )

        response = test_client.post(
            url=self.PATH,
            files={"file": ("users.csv", csv_bytes, "text/csv")},
        )

        assert response.status_code == status.HTTP_409_CONFLICT
        assert response.json() == {
            "detail": "Conflict detected for one or more fields.",
            "invalid_parameters": [
                {"name": "users[0].email", "code": "ALREADY_EXISTS"},
            ],
        }
