# coding=utf-8
"""
DNSPod Provider 单元测试
支持 Python 2.7 和 Python 3
"""

from base_test import BaseProviderTestCase, unittest, patch, MagicMock
from ddns.provider.dnspod import DnspodProvider


class TestDnspodProvider(BaseProviderTestCase):
    """DNSPod Provider 测试类"""

    def setUp(self):
        """测试初始化"""
        super(TestDnspodProvider, self).setUp()
        self.provider = DnspodProvider(self.authid, self.token)

    def test_init_with_basic_config(self):
        """Test DnspodProvider initialization with basic configuration"""
        provider = DnspodProvider(self.authid, self.token)
        self.assertEqual(provider.id, self.authid)
        self.assertEqual(provider.token, self.token)
        self.assertEqual(provider.endpoint, "https://dnsapi.cn")
        self.assertEqual(provider.DefaultLine, "默认")

    def test_class_constants(self):
        """Test DnspodProvider class constants"""
        self.assertEqual(DnspodProvider.endpoint, "https://dnsapi.cn")
        self.assertEqual(DnspodProvider.DefaultLine, "默认")
        # ContentType should be TYPE_FORM
        from ddns.provider._base import TYPE_FORM

        self.assertEqual(DnspodProvider.content_type, TYPE_FORM)

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_request_success(self, mock_http):
        """Test _request method with successful response"""
        mock_response = {"status": {"code": "1", "message": "Success"}, "data": {"test": "value"}}
        mock_http.return_value = mock_response

        result = self.provider._request("Test.Action", test_param="test_value")

        self.assertEqual(result, mock_response)
        mock_http.assert_called_once()

        # Verify request parameters
        call_args = mock_http.call_args
        self.assertEqual(call_args[0][0], "POST")  # Method
        self.assertEqual(call_args[0][1], "/Test.Action")  # URL

        # Verify body contains login token and format
        body = call_args[1]["body"]
        self.assertIn("login_token", body)
        expected_token = "{0},{1}".format(self.authid, self.token)
        self.assertEqual(body["login_token"], expected_token)
        self.assertEqual(body["format"], "json")
        self.assertEqual(body["test_param"], "test_value")

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_request_failure(self, mock_http):
        """Test _request method with failed response"""
        mock_response = {"status": {"code": "0", "message": "API Error"}}
        mock_http.return_value = mock_response

        # Mock logger to capture warning
        self.provider.logger = MagicMock()

        result = self.provider._request("Test.Action")

        self.assertEqual(result, mock_response)
        self.provider.logger.warning.assert_called_once()

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_request_filters_none_params(self, mock_http):
        """Test _request method filters out None parameters"""
        mock_response = {"status": {"code": "1"}}
        mock_http.return_value = mock_response

        self.provider._request("Test.Action", param1="value1", param2=None, param3="value3")

        body = mock_http.call_args[1]["body"]
        self.assertEqual(body["param1"], "value1")
        self.assertEqual(body["param3"], "value3")
        self.assertNotIn("param2", body)

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_request_with_extra_params(self, mock_http):
        """Test _request method with extra parameters"""
        mock_response = {"status": {"code": "1"}}
        mock_http.return_value = mock_response

        extra = {"extra_param": "extra_value"}
        self.provider._request("Test.Action", extra=extra, normal_param="normal_value")

        # Verify both extra and normal params are included
        body = mock_http.call_args[1]["body"]
        self.assertEqual(body["extra_param"], "extra_value")
        self.assertEqual(body["normal_param"], "normal_value")

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_query_zone_id_success(self, mock_http):
        """Test _query_zone_id method with successful response"""
        mock_http.return_value = {"domain": {"id": "12345", "name": "example.com"}}

        zone_id = self.provider._query_zone_id("example.com")

        self.assertEqual(zone_id, "12345")
        mock_http.assert_called_once()
        # Verify the action was correct
        call_args = mock_http.call_args
        self.assertEqual(call_args[0][1], "/Domain.Info")

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_query_zone_id_not_found(self, mock_http):
        """Test _query_zone_id method when domain is not found"""
        mock_http.return_value = {}

        zone_id = self.provider._query_zone_id("notfound.com")

        self.assertIsNone(zone_id)

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_query_record_success_single(self, mock_request):
        """Test _query_record method with single record found"""
        mock_request.return_value = {"records": [{"id": "123", "name": "www", "value": "192.168.1.1", "type": "A"}]}

        record = self.provider._query_record("zone123", "www", "example.com", "A", None, {})

        self.assertIsNotNone(record)
        if record:
            self.assertEqual(record["id"], "123")
            self.assertEqual(record["name"], "www")
        mock_request.assert_called_once_with(
            "Record.List", domain_id="zone123", sub_domain="www", record_type="A", line=None
        )

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_query_record_success_multiple(self, mock_request):
        """Test _query_record method with multiple records found"""
        mock_request.return_value = {
            "records": [
                {"id": "123", "name": "www", "value": "192.168.1.1", "type": "A"},
                {"id": "124", "name": "ftp", "value": "192.168.1.2", "type": "A"},
            ]
        }

        # Mock logger
        self.provider.logger = MagicMock()

        record = self.provider._query_record("zone123", "www", "example.com", "A", None, {})

        self.assertIsNotNone(record)
        self.assertEqual(record["name"], "www")  # type: ignore[unreachable]
        # Should log warning for multiple records
        self.provider.logger.warning.assert_called_once()

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_query_record_not_found(self, mock_request):
        """Test _query_record method when no records found"""
        mock_request.return_value = {"records": []}

        # Mock logger
        self.provider.logger = MagicMock()

        record = self.provider._query_record("zone123", "notfound", "example.com", "A", None, {})

        self.assertIsNone(record)
        self.provider.logger.warning.assert_called_once()

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_create_record_success(self, mock_request):
        """Test _create_record method with successful creation"""
        mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.1"}}

        # Mock logger
        self.provider.logger = MagicMock()

        result = self.provider._create_record(
            "zone123", "www", "example.com", "192.168.1.1", "A", ttl=600, line="电信", extra={}
        )

        self.assertTrue(result)
        self.provider.logger.info.assert_called_once()
        mock_request.assert_called_once_with(
            "Record.Create",
            extra={},
            domain_id="zone123",
            sub_domain="www",
            value="192.168.1.1",
            record_type="A",
            record_line="电信",
            ttl=600,
        )

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_create_record_with_default_line(self, mock_request):
        """Test _create_record method with default line"""
        mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.1"}}

        result = self.provider._create_record("zone123", "www", "example.com", "192.168.1.1", "A", None, None, {})

        self.assertTrue(result)
        # Should use DefaultLine when line is not specified
        call_args = mock_request.call_args[1]
        self.assertEqual(call_args["record_line"], "默认")

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_create_record_failure(self, mock_request):
        """Test _create_record method with failed creation"""
        mock_request.return_value = None

        # Mock logger
        self.provider.logger = MagicMock()

        result = self.provider._create_record("zone123", "www", "example.com", "192.168.1.1", "A", None, None, {})

        self.assertFalse(result)
        self.provider.logger.error.assert_called_once()

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_create_record_with_extra_params(self, mock_request):
        """Test _create_record method with extra parameters"""
        mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.1"}}

        extra = {"weight": 10}
        result = self.provider._create_record("zone123", "www", "example.com", "192.168.1.1", "A", None, None, extra)

        self.assertTrue(result)
        mock_request.assert_called_once_with(
            "Record.Create",
            extra=extra,
            domain_id="zone123",
            sub_domain="www",
            value="192.168.1.1",
            record_type="A",
            record_line="默认",
            ttl=None,
        )

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_update_record_success(self, mock_request):
        """Test _update_record method with successful update"""
        mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.2"}}

        old_record = {"id": "12345", "name": "www", "line": "电信"}

        # Mock logger
        self.provider.logger = MagicMock()

        result = self.provider._update_record("zone123", old_record, "192.168.1.2", "A", 300, None, {})

        self.assertTrue(result)
        self.provider.logger.debug.assert_called_once()
        mock_request.assert_called_once_with(
            "Record.Modify",
            domain_id="zone123",
            record_id="12345",
            sub_domain="www",
            record_type="A",
            value="192.168.1.2",
            record_line="电信",
            extra={},
        )

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_update_record_failure(self, mock_request):
        """Test _update_record method with failed update"""
        mock_request.return_value = None

        old_record = {"id": "12345", "name": "www"}

        # Mock logger
        self.provider.logger = MagicMock()

        result = self.provider._update_record("zone123", old_record, "192.168.1.2", "A", None, None, {})

        self.assertFalse(result)
        self.provider.logger.error.assert_called_once()

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_update_record_with_line_conversion(self, mock_request):
        """Test _update_record method with line conversion (Default -> default)"""
        mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.2"}}

        old_record = {"id": "12345", "name": "www", "line": "Default"}

        result = self.provider._update_record("zone123", old_record, "192.168.1.2", "A", None, None, {})

        self.assertTrue(result)
        # Should convert "Default" to "default"
        call_args = mock_request.call_args[1]
        self.assertEqual(call_args["record_line"], "default")

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_update_record_with_fallback_line(self, mock_request):
        """Test _update_record method with fallback to default line"""
        mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.2"}}

        old_record = {"id": "12345", "name": "www"}  # No line specified

        result = self.provider._update_record("zone123", old_record, "192.168.1.2", "A", None, None, {})

        self.assertTrue(result)
        # Should use DefaultLine when old record has no line
        call_args = mock_request.call_args[1]
        self.assertEqual(call_args["record_line"], "默认")

    @patch("ddns.provider.dnspod.DnspodProvider._request")
    def test_update_record_with_extra_params(self, mock_request):
        """Test _update_record method with extra parameters"""
        mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.2"}}

        old_record = {"id": "12345", "name": "www", "line": "电信"}
        extra = {"weight": 20}

        result = self.provider._update_record("zone123", old_record, "192.168.1.2", "A", None, None, extra)

        self.assertTrue(result)
        call_args = mock_request.call_args[1]
        self.assertEqual(call_args["extra"], extra)

    def test_request_with_none_response(self):
        """Test _request method when HTTP returns None"""
        with patch("ddns.provider.dnspod.DnspodProvider._http") as mock_http:
            mock_http.return_value = None
            self.provider.logger = MagicMock()

            # Should return None and log a warning
            result = self.provider._request("Test.Action")

            self.assertIsNone(result)
            # Verify warning was logged
            self.provider.logger.warning.assert_called_once()

    def test_create_record_with_no_record_in_response(self):
        """Test _create_record method when response has no record field"""
        with patch("ddns.provider.dnspod.DnspodProvider._request") as mock_request:
            mock_request.return_value = {"status": {"code": "1"}}  # No record field
            self.provider.logger = MagicMock()

            result = self.provider._create_record("zone123", "www", "example.com", "192.168.1.1", "A", None, None, {})

            self.assertFalse(result)
            self.provider.logger.error.assert_called_once()

    def test_update_record_with_no_record_in_response(self):
        """Test _update_record method when response has no record field"""
        with patch("ddns.provider.dnspod.DnspodProvider._request") as mock_request:
            mock_request.return_value = {"status": {"code": "1"}}  # No record field
            old_record = {"id": "12345", "name": "www"}
            self.provider.logger = MagicMock()

            result = self.provider._update_record("zone123", old_record, "192.168.1.2", "A", None, None, {})

            self.assertFalse(result)
            self.provider.logger.error.assert_called_once()

    def test_line_configuration_support(self):
        """Test that DnspodProvider supports line configuration"""
        with patch("ddns.provider.dnspod.DnspodProvider._request") as mock_request:
            mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.1"}}

            # Test create record with line parameter
            result = self.provider._create_record("zone123", "www", "example.com", "192.168.1.1", "A", 600, "电信", {})

            self.assertTrue(result)
            mock_request.assert_called_once_with(
                "Record.Create",
                extra={},
                domain_id="zone123",
                sub_domain="www",
                value="192.168.1.1",
                record_type="A",
                record_line="电信",
                ttl=600,
            )

    def test_update_record_with_custom_line(self):
        """Test _update_record method with custom line parameter"""
        with patch("ddns.provider.dnspod.DnspodProvider._request") as mock_request:
            mock_request.return_value = {"record": {"id": "12345", "name": "www", "value": "192.168.1.2"}}

            old_record = {"id": "12345", "name": "www", "line": "默认"}

            # Test with custom line parameter
            result = self.provider._update_record("zone123", old_record, "192.168.1.2", "A", 300, "联通", {})

            self.assertTrue(result)
            call_args = mock_request.call_args[1]
            self.assertEqual(call_args["record_line"], "联通")


class TestDnspodProviderIntegration(BaseProviderTestCase):
    """DNSPod Provider 集成测试类"""

    def setUp(self):
        """测试初始化"""
        super(TestDnspodProviderIntegration, self).setUp()
        self.provider = DnspodProvider(self.authid, self.token)
        self.provider.logger = MagicMock()

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_full_workflow_create_record(self, mock_http):
        """Test complete workflow for creating a new record"""
        # Mock HTTP responses for the workflow
        responses = [
            # Domain.Info response
            {"status": {"code": "1"}, "domain": {"id": "zone123"}},
            # Record.List response (no existing records)
            {"status": {"code": "1"}, "records": []},
            # Record.Create response
            {"status": {"code": "1"}, "record": {"id": "rec123", "name": "www", "value": "192.168.1.1"}},
        ]
        mock_http.side_effect = responses

        result = self.provider.set_record("www.example.com", "192.168.1.1")

        self.assertTrue(result)
        self.assertEqual(mock_http.call_count, 3)

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_full_workflow_update_record(self, mock_http):
        """Test complete workflow for updating an existing record"""
        # Mock HTTP responses for the workflow
        responses = [
            # Domain.Info response
            {"status": {"code": "1"}, "domain": {"id": "zone123"}},
            # Record.List response (existing record found)
            {
                "status": {"code": "1"},
                "records": [{"id": "rec123", "name": "www", "value": "192.168.1.100", "line": "默认"}],
            },
            # Record.Modify response
            {"status": {"code": "1"}, "record": {"id": "rec123", "name": "www", "value": "192.168.1.1"}},
        ]
        mock_http.side_effect = responses

        result = self.provider.set_record("www.example.com", "192.168.1.1")

        self.assertTrue(result)
        self.assertEqual(mock_http.call_count, 3)

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_full_workflow_zone_not_found(self, mock_http):
        """Test complete workflow when zone is not found"""
        # Domain.Info response - no domain found
        mock_http.return_value = {"status": {"code": "0", "message": "Domain not found"}}

        # Should return False when zone not found
        result = self.provider.set_record("www.notfound.com", "192.168.1.1")
        self.assertFalse(result)

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_full_workflow_create_failure(self, mock_http):
        """Test complete workflow when record creation fails"""
        responses = [
            # Domain.Info response
            {"status": {"code": "1"}, "domain": {"id": "zone123"}},
            # Record.List response (no existing records)
            {"status": {"code": "1"}, "records": []},
            # Record.Create response (failure)
            {"status": {"code": "0", "message": "Create failed"}},
        ]
        mock_http.side_effect = responses

        result = self.provider.set_record("www.example.com", "192.168.1.1")

        self.assertFalse(result)
        self.assertEqual(mock_http.call_count, 3)

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_full_workflow_update_failure(self, mock_http):
        """Test complete workflow when record update fails"""
        responses = [
            # Domain.Info response
            {"status": {"code": "1"}, "domain": {"id": "zone123"}},
            # Record.List response (existing record found)
            {
                "status": {"code": "1"},
                "records": [{"id": "rec123", "name": "www", "value": "192.168.1.100", "line": "默认"}],
            },
            # Record.Modify response (failure)
            {"status": {"code": "0", "message": "Update failed"}},
        ]
        mock_http.side_effect = responses

        result = self.provider.set_record("www.example.com", "192.168.1.1")

        self.assertFalse(result)
        self.assertEqual(mock_http.call_count, 3)

    @patch("ddns.provider.dnspod.DnspodProvider._http")
    def test_full_workflow_with_options(self, mock_http):
        """Test complete workflow with additional options like ttl and line"""
        responses = [
            # Domain.Info response
            {"status": {"code": "1"}, "domain": {"id": "zone123"}},
            # Record.List response (no existing records)
            {"status": {"code": "1"}, "records": []},
            # Record.Create response
            {"status": {"code": "1"}, "record": {"id": "rec123", "name": "www", "value": "192.168.1.1"}},
        ]
        mock_http.side_effect = responses

        result = self.provider.set_record("www.example.com", "192.168.1.1", record_type="A", ttl=300, line="电信")

        self.assertTrue(result)
        self.assertEqual(mock_http.call_count, 3)

        # Verify the Record.Create call includes the custom options
        create_call = mock_http.call_args_list[2]
        body = create_call[1]["body"]
        self.assertEqual(body["ttl"], 300)
        self.assertEqual(body["record_line"], "电信")


class TestDnspodProviderRealRequest(BaseProviderTestCase):
    """DNSPod Provider 真实请求测试类"""

    def test_auth_failure_real_request(self):
        """Test authentication failure with real API request"""
        # 使用无效的认证信息创建 provider
        invalid_provider = DnspodProvider("invalid_id", "invalid_token")

        # 尝试查询域名信息，应该抛出认证失败异常
        with self.assertRaises(RuntimeError) as cm:
            invalid_provider._query_zone_id("example.com")

        # 验证异常信息包含认证失败 - 更新错误消息格式
        error_message = str(cm.exception)
        self.assertTrue(
            "认证失败" in error_message and "401" in error_message,
            "Expected authentication error message not found in: {}".format(error_message),
        )


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