from io import StringIO

from django.core.management import call_command
from django.test import TestCase
from django.core.exceptions import ValidationError

from .models import (
    Merchant, MerchantTypeEnum, WareHouse, WarehouseTypeEnum, 
    WareHouseModeEnum, EmployeeType, Employee, EmployeeStatusEnum,
    FrontendPage, FrontendPageTypeEnum, TransportVehicle, TransportVehicleMaterialCapacity
)


class WarehouseTypeTestCase(TestCase):
    """测试仓库类型字段"""

    def setUp(self):
        """设置测试数据"""
        self.merchant = Merchant.objects.create(
            name='测试商户',
            type=MerchantTypeEnum.STORE
        )

    def test_warehouse_default_type(self):
        """测试仓库类型的默认值"""
        warehouse = WareHouse.objects.create(
            merchant=self.merchant,
            name='默认仓库',
        )
        self.assertEqual(warehouse.type, WarehouseTypeEnum.WHOLE)

    def test_warehouse_type_whole(self):
        """测试创建整仓"""
        warehouse = WareHouse.objects.create(
            merchant=self.merchant,
            name='整仓1',
            type=WarehouseTypeEnum.WHOLE
        )
        self.assertEqual(warehouse.type, WarehouseTypeEnum.WHOLE)
        self.assertEqual(warehouse.get_type_display(), '整仓')

    def test_warehouse_type_scattered(self):
        """测试创建散仓"""
        warehouse = WareHouse.objects.create(
            merchant=self.merchant,
            name='散仓1',
            type=WarehouseTypeEnum.SCATTERED
        )
        self.assertEqual(warehouse.type, WarehouseTypeEnum.SCATTERED)
        self.assertEqual(warehouse.get_type_display(), '散仓')

    def test_warehouse_type_enum_values(self):
        """测试仓库类型枚举的所有值"""
        self.assertEqual(WarehouseTypeEnum.WHOLE.value, 1)
        self.assertEqual(WarehouseTypeEnum.WHOLE.label, '整仓')
        self.assertEqual(WarehouseTypeEnum.SCATTERED.value, 2)
        self.assertEqual(WarehouseTypeEnum.SCATTERED.label, '散仓')

    def test_warehouse_with_all_fields(self):
        """测试带有所有字段的仓库"""
        warehouse = WareHouse.objects.create(
            merchant=self.merchant,
            name='完整仓库',
            type=WarehouseTypeEnum.SCATTERED,
            mode=WareHouseModeEnum.RESTRICT_IN_OUT,
            location='测试地址',
            area='测试区域',
            contact='张三',
            mobile='13800138000',
            description='测试描述'
        )
        self.assertEqual(warehouse.type, WarehouseTypeEnum.SCATTERED)
        self.assertEqual(warehouse.mode, WareHouseModeEnum.RESTRICT_IN_OUT)
        self.assertEqual(warehouse.name, '完整仓库')


class EmployeeTypeTestCase(TestCase):
    """测试员工职位类型"""

    def setUp(self):
        """设置测试数据"""
        self.merchant = Merchant.objects.create(
            name='测试商户',
            type=MerchantTypeEnum.STORE
        )

    def test_create_employee_type(self):
        """测试创建员工职位类型"""
        emp_type = EmployeeType.objects.create(
            merchant=self.merchant,
            title='测试职位',
            description='职位描述'
        )
        self.assertEqual(emp_type.title, '测试职位')
        self.assertEqual(emp_type.description, '职位描述')
        self.assertEqual(str(emp_type), '测试职位')

    def test_employee_type_unique_together(self):
        """测试商户+职位名称的唯一性约束"""
        EmployeeType.objects.create(
            merchant=self.merchant,
            title='打纸'
        )
        
        # 同一商户不能创建重复职位
        with self.assertRaises(Exception):
            EmployeeType.objects.create(
                merchant=self.merchant,
                title='打纸'
            )

    def test_employee_with_position(self):
        """测试员工关联职位"""
        emp_type = EmployeeType.objects.create(
            merchant=self.merchant,
            title='滚筒工'
        )
        
        employee = Employee.objects.create(
            merchant=self.merchant,
            name='张三',
            position=emp_type,
            status=EmployeeStatusEnum.ACTIVE
        )
        
        self.assertEqual(employee.position, emp_type)
        self.assertEqual(employee.job_type, '滚筒工')  # 测试 job_type 属性

    def test_employee_without_position(self):
        """测试员工没有职位时 job_type 返回空字符串"""
        employee = Employee.objects.create(
            merchant=self.merchant,
            name='李四',
            status=EmployeeStatusEnum.ACTIVE
        )
        
        self.assertIsNone(employee.position)
        self.assertEqual(employee.job_type, '')

    def test_employee_type_reverse_field(self):
        """测试预留字段"""
        emp_type = EmployeeType.objects.create(
            merchant=self.merchant,
            title='仓库管理员',
            reverse='预留数据'
        )
        
        self.assertEqual(emp_type.reverse, '预留数据')

    def test_multiple_employees_same_position(self):
        """测试多个员工可以有相同职位"""
        emp_type = EmployeeType.objects.create(
            merchant=self.merchant,
            title='打纸工'
        )
        
        emp1 = Employee.objects.create(
            merchant=self.merchant,
            name='员工1',
            position=emp_type
        )
        
        emp2 = Employee.objects.create(
            merchant=self.merchant,
            name='员工2',
            position=emp_type
        )
        
        self.assertEqual(emp1.job_type, '打纸工')
        self.assertEqual(emp2.job_type, '打纸工')
        self.assertEqual(emp_type.employees.count(), 2)


class TransportVehicleTestCase(TestCase):
    def setUp(self):
        self.merchant = Merchant.objects.create(
            name='运输商户',
            type=MerchantTypeEnum.STORE
        )

    def test_create_transport_vehicle_with_material_capacity(self):
        vehicle = TransportVehicle.objects.create(
            merchant=self.merchant,
            name='9.6米厢车',
            license_plate='浙A12345'
        )
        capacity = TransportVehicleMaterialCapacity.objects.create(
            merchant=self.merchant,
            transport_vehicle=vehicle,
            material_name='棉布',
            capacity=120
        )

        self.assertEqual(vehicle.license_plate, '浙A12345')
        self.assertEqual(str(vehicle), '9.6米厢车 (浙A12345)')
        self.assertEqual(capacity.capacity, 120)
        self.assertEqual(vehicle.material_capacities.count(), 1)

    def test_transport_vehicle_license_plate_unique_per_merchant(self):
        TransportVehicle.objects.create(
            merchant=self.merchant,
            name='一号车',
            license_plate='浙A99999'
        )
        with self.assertRaises(Exception):
            TransportVehicle.objects.create(
                merchant=self.merchant,
                name='二号车',
                license_plate='浙A99999'
            )


class FrontendPageTestCase(TestCase):
    """测试前端页面模型"""

    def test_create_main_menu(self):
        """测试创建主菜单"""
        page = FrontendPage.objects.create(
            key='plate-opening',
            label='开版管理',
            page_type=FrontendPageTypeEnum.MAIN,
            sort_order=0
        )
        self.assertEqual(page.key, 'plate-opening')
        self.assertEqual(page.label, '开版管理')
        self.assertEqual(page.page_type, FrontendPageTypeEnum.MAIN)
        self.assertIsNone(page.parent)
        self.assertTrue(page.is_active)
        self.assertEqual(str(page), '开版管理 (plate-opening)')

    def test_create_sub_menu_with_parent(self):
        """测试创建子菜单并关联父级"""
        parent = FrontendPage.objects.create(
            key='plate-opening',
            label='开版管理',
            page_type=FrontendPageTypeEnum.MAIN
        )
        child = FrontendPage.objects.create(
            key='plate-order',
            label='开版订单',
            page_type=FrontendPageTypeEnum.SUB,
            parent=parent
        )
        self.assertEqual(child.parent, parent)
        self.assertEqual(parent.children.count(), 1)
        self.assertEqual(parent.children.first(), child)

    def test_key_unique_constraint(self):
        """测试 key 字段唯一性约束"""
        FrontendPage.objects.create(key='test-page', label='测试页面')
        with self.assertRaises(Exception):
            FrontendPage.objects.create(key='test-page', label='重复页面')

    def test_cascade_delete(self):
        """测试删除父级时子级也被删除"""
        parent = FrontendPage.objects.create(
            key='parent',
            label='父级',
            page_type=FrontendPageTypeEnum.MAIN
        )
        FrontendPage.objects.create(
            key='child1',
            label='子级1',
            parent=parent
        )
        FrontendPage.objects.create(
            key='child2',
            label='子级2',
            parent=parent
        )
        self.assertEqual(FrontendPage.objects.count(), 3)
        parent.delete()
        self.assertEqual(FrontendPage.objects.count(), 0)


class EmployeeTypeVisiblePagesTestCase(TestCase):
    """测试员工职位类型的可见页面功能"""

    def setUp(self):
        """设置测试数据"""
        self.merchant = Merchant.objects.create(
            name='测试商户',
            type=MerchantTypeEnum.STORE
        )
        # 创建页面
        self.main_menu = FrontendPage.objects.create(
            key='production',
            label='生产管理',
            page_type=FrontendPageTypeEnum.MAIN
        )
        self.sub_menu1 = FrontendPage.objects.create(
            key='production-kanban',
            label='生产看板',
            page_type=FrontendPageTypeEnum.SUB,
            parent=self.main_menu
        )
        self.sub_menu2 = FrontendPage.objects.create(
            key='printing-order-list',
            label='生产订单',
            page_type=FrontendPageTypeEnum.SUB,
            parent=self.main_menu
        )
        # 创建岗位
        self.emp_type = EmployeeType.objects.create(
            merchant=self.merchant,
            title='生产主管'
        )

    def test_assign_visible_pages(self):
        """测试为岗位分配可见页面"""
        self.emp_type.visible_pages.add(self.main_menu)
        self.assertEqual(self.emp_type.visible_pages.count(), 1)
        self.assertIn(self.main_menu, self.emp_type.visible_pages.all())

    def test_get_visible_page_keys_with_main_menu(self):
        """测试授予主菜单时获取所有可见页面（含子菜单）"""
        self.emp_type.visible_pages.add(self.main_menu)
        
        # 模拟查询逻辑
        visible_keys = set()
        for page in self.emp_type.visible_pages.filter(is_active=True):
            visible_keys.add(page.key)
            if page.page_type == FrontendPageTypeEnum.MAIN:
                children_keys = page.children.filter(is_active=True).values_list('key', flat=True)
                visible_keys.update(children_keys)
        
        self.assertIn('production', visible_keys)
        self.assertIn('production-kanban', visible_keys)
        self.assertIn('printing-order-list', visible_keys)
        self.assertEqual(len(visible_keys), 3)

    def test_get_visible_page_keys_with_sub_menu_only(self):
        """测试仅授予子菜单时只能看到该子菜单"""
        self.emp_type.visible_pages.add(self.sub_menu1)
        
        visible_keys = set()
        for page in self.emp_type.visible_pages.filter(is_active=True):
            visible_keys.add(page.key)
            if page.page_type == FrontendPageTypeEnum.MAIN:
                children_keys = page.children.filter(is_active=True).values_list('key', flat=True)
                visible_keys.update(children_keys)
        
        self.assertIn('production-kanban', visible_keys)
        self.assertNotIn('production', visible_keys)
        self.assertNotIn('printing-order-list', visible_keys)
        self.assertEqual(len(visible_keys), 1)

    def test_employee_visible_pages_through_position(self):
        """测试通过员工岗位查询可见页面"""
        self.emp_type.visible_pages.add(self.main_menu)
        
        employee = Employee.objects.create(
            merchant=self.merchant,
            name='测试员工',
            position=self.emp_type
        )
        
        # 通过员工 -> 岗位 -> 可见页面
        visible_pages = employee.position.visible_pages.all()
        self.assertEqual(visible_pages.count(), 1)
        self.assertIn(self.main_menu, visible_pages)


class SyncFrontendPagesCommandTestCase(TestCase):
    """测试 sync_frontend_pages 管理命令"""

    def test_sync_command_dry_run(self):
        """测试 dry-run 模式不写入数据"""
        out = StringIO()
        call_command('sync_frontend_pages', '--dry-run', stdout=out)
        
        # 验证没有实际写入
        self.assertEqual(FrontendPage.objects.count(), 0)
        self.assertIn('DRY-RUN', out.getvalue())

    def test_sync_command_creates_pages(self):
        """测试命令创建页面数据"""
        out = StringIO()
        call_command('sync_frontend_pages', stdout=out)
        
        # 验证创建了页面
        self.assertGreater(FrontendPage.objects.count(), 0)
        
        # 验证主菜单
        main_menus = FrontendPage.objects.filter(page_type=FrontendPageTypeEnum.MAIN)
        self.assertEqual(main_menus.count(), 9)  # 9 个主菜单
        
        # 验证子菜单
        sub_menus = FrontendPage.objects.filter(page_type=FrontendPageTypeEnum.SUB)
        self.assertEqual(sub_menus.count(), 43)  # 43 个子菜单
        
        # 验证父子关系
        plate_opening = FrontendPage.objects.get(key='plate-opening')
        self.assertEqual(plate_opening.children.count(), 5)

    def test_sync_command_idempotent(self):
        """测试命令幂等性（多次运行结果一致）"""
        call_command('sync_frontend_pages', stdout=StringIO())
        first_count = FrontendPage.objects.count()
        
        call_command('sync_frontend_pages', stdout=StringIO())
        second_count = FrontendPage.objects.count()
        
        self.assertEqual(first_count, second_count)

    def test_sync_command_clear_option(self):
        """测试 --clear 选项"""
        # 先创建数据
        call_command('sync_frontend_pages', stdout=StringIO())
        self.assertGreater(FrontendPage.objects.count(), 0)
        
        # 使用 --clear 重新同步
        out = StringIO()
        call_command('sync_frontend_pages', '--clear', stdout=out)
        
        self.assertIn('已清空', out.getvalue())
        # 数据应该重新创建
        self.assertEqual(FrontendPage.objects.count(), 52)  # 9 主菜单 + 43 子菜单
