"""
BusinessObject 克隆服务测试

重点验证：
- 克隆后新对象与源对象业务数据一致（process/描述/关联对象/日志/参数）
- 时间戳（completed_at/cancelled_at/参数记录 created_at 等）保持一致
- 包含已撤销记录
- 克隆对象与原对象互不影响
"""

import datetime

from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.utils import timezone

from stateflow import models, services

User = get_user_model()


class CloneBusinessObjectServiceTestCase(TestCase):
    def setUp(self):
        self.user1 = User.objects.create_user(username='u1', password='pass')
        self.user2 = User.objects.create_user(username='u2', password='pass')

        self.state1 = models.State.objects.create(name='状态1', description='第一个节点')
        self.state2 = models.State.objects.create(name='状态2', description='第二个节点')

        self.process = models.Process.objects.create(name='流程A', description='流程描述')
        models.ProcessNode.objects.create(process=self.process, state=self.state1, order=0)
        models.ProcessNode.objects.create(process=self.process, state=self.state2, order=1)

        ct = ContentType.objects.get_for_model(models.Process)
        self.business_object = models.BusinessObject.objects.create(
            name='BO-1',
            process=self.process,
            description='BO 描述',
            content_type=ct,
            object_id=self.process.id,
        )

        # 固定时间点
        tz = timezone.get_default_timezone()
        self.t1 = timezone.make_aware(datetime.datetime(2025, 12, 1, 10, 0, 0), tz)
        self.t1a = timezone.make_aware(datetime.datetime(2025, 12, 1, 10, 1, 0), tz)
        self.t1b = timezone.make_aware(datetime.datetime(2025, 12, 1, 10, 2, 0), tz)
        self.t2 = timezone.make_aware(datetime.datetime(2025, 12, 2, 9, 0, 0), tz)
        self.t2a = timezone.make_aware(datetime.datetime(2025, 12, 2, 9, 1, 0), tz)
        self.t2_cancel = timezone.make_aware(datetime.datetime(2025, 12, 2, 9, 30, 0), tz)

        # 创建两条状态日志：一条正常，一条已撤销
        log1 = models.StateFlowRecord.objects.create(
            business_object=self.business_object,
            state=self.state1,
            completed_by=self.user1,
            is_cancelled=False,
        )
        models.StateFlowRecord.objects.filter(id=log1.id).update(
            completed_at=self.t1,
            created_at=self.t1,
            updated_at=self.t1,
        )

        rec1 = models.StateLogParameterRecord.objects.create(
            state_log=log1,
            parameters={'temperature': '25.5'},
            remark='初次测量',
        )
        models.StateLogParameterRecord.objects.filter(id=rec1.id).update(
            created_at=self.t1a,
            updated_at=self.t1a,
        )

        rec2 = models.StateLogParameterRecord.objects.create(
            state_log=log1,
            parameters={'temperature': '26.0', 'operator': '张三'},
            remark='复测',
        )
        models.StateLogParameterRecord.objects.filter(id=rec2.id).update(
            created_at=self.t1b,
            updated_at=self.t1b,
        )

        log2 = models.StateFlowRecord.objects.create(
            business_object=self.business_object,
            state=self.state2,
            completed_by=self.user2,
            is_cancelled=True,
            cancelled_at=self.t2_cancel,
        )
        models.StateFlowRecord.objects.filter(id=log2.id).update(
            completed_at=self.t2,
            created_at=self.t2,
            updated_at=self.t2,
            cancelled_at=self.t2_cancel,
        )

        rec3 = models.StateLogParameterRecord.objects.create(
            state_log=log2,
            parameters={'humidity': '60%'},
            remark='记录湿度',
        )
        models.StateLogParameterRecord.objects.filter(id=rec3.id).update(
            created_at=self.t2a,
            updated_at=self.t2a,
        )

        # 重新加载，确保测试数据时间戳生效
        self.business_object.refresh_from_db()

    def test_clone_business_object_copies_all_records_and_parameters(self):
        new_process = models.Process.objects.create(name='流程B', description='克隆目标流程')
        new_object_id = new_process.id
        cloned = services.clone_business_object(
            self.business_object,
            new_object_id=new_object_id,
            expected_content_type_id=self.business_object.content_type_id,
        )

        # 基本字段一致
        self.assertNotEqual(cloned.id, self.business_object.id)
        self.assertEqual(cloned.name, self.business_object.name)
        self.assertEqual(cloned.description, self.business_object.description)
        self.assertEqual(cloned.process_id, self.business_object.process_id)
        self.assertEqual(cloned.content_type_id, self.business_object.content_type_id)
        self.assertEqual(cloned.object_id, new_object_id)
        self.assertNotEqual(cloned.object_id, self.business_object.object_id)

        # ModelBase 时间戳字段同步（严格一致）
        self.assertEqual(cloned.created_at, self.business_object.created_at)
        self.assertEqual(cloned.updated_at, self.business_object.updated_at)

        src_logs = list(self.business_object.state_logs.all().order_by('completed_at', 'id'))
        new_logs = list(cloned.state_logs.all().order_by('completed_at', 'id'))
        self.assertEqual(len(new_logs), len(src_logs))

        for src, new in zip(src_logs, new_logs):
            self.assertNotEqual(src.id, new.id)
            self.assertEqual(new.state_id, src.state_id)
            self.assertEqual(new.completed_by_id, src.completed_by_id)
            self.assertEqual(new.is_cancelled, src.is_cancelled)
            self.assertEqual(new.cancelled_at, src.cancelled_at)
            self.assertEqual(new.completed_at, src.completed_at)
            self.assertEqual(new.created_at, src.created_at)
            self.assertEqual(new.updated_at, src.updated_at)

            src_params = list(src.parameter_records.all().order_by('created_at', 'id'))
            new_params = list(new.parameter_records.all().order_by('created_at', 'id'))
            self.assertEqual(len(new_params), len(src_params))

            for src_rec, new_rec in zip(src_params, new_params):
                self.assertNotEqual(src_rec.id, new_rec.id)
                self.assertEqual(new_rec.parameters, src_rec.parameters)
                self.assertEqual(new_rec.remark, src_rec.remark)
                self.assertEqual(new_rec.created_at, src_rec.created_at)
                self.assertEqual(new_rec.updated_at, src_rec.updated_at)

        # 已撤销记录的参数默认不可见（克隆也应一致）
        src_cancelled = src_logs[1]
        new_cancelled = new_logs[1]
        self.assertTrue(src_cancelled.is_cancelled)
        self.assertTrue(new_cancelled.is_cancelled)
        self.assertEqual(src_cancelled.get_all_parameters_summary(), {})
        self.assertEqual(new_cancelled.get_all_parameters_summary(), {})
        self.assertEqual(
            new_cancelled.get_all_parameters_summary(include_cancelled=True),
            src_cancelled.get_all_parameters_summary(include_cancelled=True),
        )

    def test_clone_is_independent_from_source(self):
        target_process = models.Process.objects.create(name='流程B-独立', description='新的绑定对象')
        cloned = services.clone_business_object(
            self.business_object,
            new_object_id=target_process.id,
            expected_content_type_id=self.business_object.content_type_id,
        )

        # 修改克隆对象的参数记录，不应影响源对象
        cloned_first_log = cloned.state_logs.order_by('completed_at', 'id').first()
        cloned_first_param = cloned_first_log.parameter_records.order_by('created_at', 'id').first()

        cloned_first_param.parameters['temperature'] = '99.9'
        cloned_first_param.save(update_fields=['parameters'])

        src_first_log = self.business_object.state_logs.order_by('completed_at', 'id').first()
        src_first_param = src_first_log.parameter_records.order_by('created_at', 'id').first()
        self.assertEqual(src_first_param.parameters['temperature'], '25.5')

    def test_clone_empty_business_object_requires_binding(self):
        bo = models.BusinessObject.objects.create(
            name='BO-empty',
            process=self.process,
            description='empty',
        )
        with self.assertRaises(ValueError):
            services.clone_business_object(
                bo,
                new_object_id=None,
                expected_content_type_id=None,
            )

    def test_clone_rejects_mismatched_content_type(self):
        """content_type 不一致应拒绝克隆"""
        # 用另一个 ContentType（比如 State）
        ct_state = ContentType.objects.get_for_model(models.State)
        self.assertNotEqual(ct_state.id, self.business_object.content_type_id)

        with self.assertRaises(ValueError):
            services.clone_business_object(
                self.business_object,
                new_object_id=(self.business_object.object_id or 0) + 1000,
                expected_content_type_id=ct_state.id,
            )

    def test_clone_requires_new_object_id_when_content_type_present(self):
        """源对象有 content_type 时，必须提供新的 object_id"""
        with self.assertRaises(ValueError):
            services.clone_business_object(
                self.business_object,
                new_object_id=None,
                expected_content_type_id=self.business_object.content_type_id,
            )

    def test_clone_rejects_same_object_id(self):
        """object_id 与源对象相同应拒绝克隆"""
        with self.assertRaises(ValueError):
            services.clone_business_object(
                self.business_object,
                new_object_id=self.business_object.object_id,
                expected_content_type_id=self.business_object.content_type_id,
            )


