"""批量换货脚本的输入校验测试。"""
from __future__ import annotations

import importlib.util
import sys
import tempfile
import unittest
from pathlib import Path


SCRIPT_PATH = Path(__file__).with_name("batch_exchange.py")
SPEC = importlib.util.spec_from_file_location("batch_exchange", SCRIPT_PATH)
assert SPEC is not None and SPEC.loader is not None
batch_exchange = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = batch_exchange
SPEC.loader.exec_module(batch_exchange)


class LoadExchangesTest(unittest.TestCase):
    """验证 CSV 映射校验不会把危险批次交给接口执行。"""

    def write_csv(self, content: str) -> Path:
        """在临时目录中创建待解析的 CSV。"""
        directory = tempfile.TemporaryDirectory()
        self.addCleanup(directory.cleanup)
        path = Path(directory.name) / "exchanges.csv"
        path.write_text(content, encoding="utf-8")
        return path

    def test_loads_supported_header_and_rows(self) -> None:
        """支持标准表头并保留原始行号。"""
        path = self.write_csv("old_identifier,new_identifier\nold-1,new-1\nold-2,new-2\n")

        exchanges = batch_exchange.load_exchanges(path)

        self.assertEqual(
            exchanges,
            [
                batch_exchange.ExchangeInput(2, "old-1", "new-1"),
                batch_exchange.ExchangeInput(3, "old-2", "new-2"),
            ],
        )

    def test_rejects_duplicate_old_identifier(self) -> None:
        """同一旧资产不能在一批中换出两次。"""
        path = self.write_csv("old-1,new-1\nold-1,new-2\n")

        with self.assertRaisesRegex(ValueError, "旧资产.*重复"):
            batch_exchange.load_exchanges(path)

    def test_rejects_duplicate_new_identifier(self) -> None:
        """同一新资产不能接收两次换货。"""
        path = self.write_csv("old-1,new-1\nold-2,new-1\n")

        with self.assertRaisesRegex(ValueError, "新资产.*重复"):
            batch_exchange.load_exchanges(path)

    def test_rejects_same_identifier_in_one_row(self) -> None:
        """禁止资产换给自身。"""
        path = self.write_csv("old-1,old-1\n")

        with self.assertRaisesRegex(ValueError, "新旧资产标识相同"):
            batch_exchange.load_exchanges(path)

    def test_rejects_cross_row_asset_reuse(self) -> None:
        """禁止资产在同批中同时作为旧资产和新资产。"""
        path = self.write_csv("old-1,new-1\nnew-1,new-2\n")

        with self.assertRaisesRegex(ValueError, "同时作为.*旧资产.*新资产"):
            batch_exchange.load_exchanges(path)

    def test_rejects_missing_column_value(self) -> None:
        """双列中的空值必须在请求前被拦截。"""
        path = self.write_csv("old_identifier,new_identifier\nold-1,\n")

        with self.assertRaisesRegex(ValueError, "均不能为空"):
            batch_exchange.load_exchanges(path)


class BuildExchangePayloadTest(unittest.TestCase):
    """验证脚本不能关闭直接换货或数据迁移。"""

    def test_forces_direct_flow_and_migration(self) -> None:
        """请求固定为 direct 且 migrate_data 为 true。"""
        exchange = batch_exchange.ExchangeInput(2, "old-1", "new-1")

        payload = batch_exchange.build_exchange_payload(
            exchange,
            "iot_card",
            "批量直接换货",
            None,
        )

        self.assertEqual(payload["flow_type"], "direct")
        self.assertIs(payload["migrate_data"], True)
        self.assertEqual(payload["old_asset_type"], "iot_card")


class ExchangeAssetTest(unittest.TestCase):
    """验证结果必须同时满足接口成功和迁移完成。"""

    class FakeClient:
        """返回指定响应的换货客户端替身。"""

        def __init__(self, result: object) -> None:
            self.result = result

        def create_exchange(self, *args: object) -> object:
            """返回测试预设的接口响应。"""
            return self.result

    def test_marks_completed_migration_as_success(self) -> None:
        """业务成功且迁移完成时结果为成功。"""
        result = batch_exchange.HTTPResult(
            status=200,
            body={
                "code": 0,
                "msg": "success",
                "data": {
                    "id": 123,
                    "exchange_no": "EX123",
                    "migration_completed": True,
                    "migration_balance": 100,
                },
            },
            raw_body="",
        )

        outcome = batch_exchange.exchange_asset(
            self.FakeClient(result),
            "token",
            batch_exchange.ExchangeInput(2, "old-1", "new-1"),
            "iot_card",
            "批量直接换货",
            None,
        )

        self.assertTrue(outcome.success)
        self.assertEqual(outcome.exchange_no, "EX123")

    def test_rejects_success_response_without_completed_migration(self) -> None:
        """业务成功但未确认迁移完成时结果仍为失败。"""
        result = batch_exchange.HTTPResult(
            status=200,
            body={
                "code": 0,
                "msg": "success",
                "data": {
                    "id": 123,
                    "exchange_no": "EX123",
                    "migration_completed": False,
                    "migration_balance": 0,
                },
            },
            raw_body="",
        )

        outcome = batch_exchange.exchange_asset(
            self.FakeClient(result),
            "token",
            batch_exchange.ExchangeInput(2, "old-1", "new-1"),
            "iot_card",
            "批量直接换货",
            None,
        )

        self.assertFalse(outcome.success)
        self.assertIn("未确认数据迁移完成", outcome.message)


if __name__ == "__main__":
    unittest.main()
