import bpy
import bmesh
import math
from mathutils import Vector, Matrix

def clear_scene():
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete(use_global=False)
    for mesh in bpy.data.meshes:
        bpy.data.meshes.remove(mesh)
    for mat in bpy.data.materials:
        bpy.data.materials.remove(mat)

def create_material(name, base_color, metallic=0.0, roughness=0.5, subsurface=0.0):
    mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    principled = nodes.get("Principled BSDF")
    
    if principled:
        try:
            principled.inputs["Base Color"].default_value = tuple(base_color)
        except Exception:
            pass
        try:
            principled.inputs["Metallic"].default_value = float(metallic)
        except Exception:
            pass
        try:
            principled.inputs["Roughness"].default_value = float(roughness)
        except Exception:
            pass
        try:
            if "Subsurface Weight" in principled.inputs:
                principled.inputs["Subsurface Weight"].default_value = float(subsurface)
            elif "Subsurface" in principled.inputs:
                principled.inputs["Subsurface"].default_value = float(subsurface)
        except Exception:
            pass
    
    return mat

def apply_mat_to_obj(obj, material):
    if obj and obj.data:
        if len(obj.data.materials) == 0:
            obj.data.materials.append(material)
        else:
            obj.data.materials[0] = material

def create_body():
    bpy.ops.mesh.primitive_uv_sphere_add(segments=48, ring_count=24, radius=0.35, location=(0, 0, 0.4))
    body = bpy.context.active_object
    if not body:
        return None
    body.name = "Duck_Body"
    
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(body.data)
    
    for v in bm.verts:
        y_factor = v.co.y / 0.35
        v.co.y *= 2.2
        v.co.x *= 1.0 + 0.15 * (1 - abs(y_factor))
        v.co.z *= 0.85
        if v.co.y > 0.3:
            v.co.z += 0.08 * (v.co.y - 0.3)
            v.co.x *= 1.0 - 0.2 * ((v.co.y - 0.3) / 0.5)
        if v.co.y < -0.4:
            v.co.z -= 0.05 * abs(v.co.y + 0.4)
            v.co.x *= 0.7
    
    bmesh.update_edit_mesh(body.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    
    return bpy.data.objects.get("Duck_Body")

def create_breast():
    bpy.ops.mesh.primitive_uv_sphere_add(segments=32, ring_count=16, radius=0.28, location=(0, 0.45, 0.32))
    breast = bpy.context.active_object
    if not breast:
        return None
    breast.name = "Duck_Breast"
    
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(breast.data)
    
    for v in bm.verts:
        v.co.y *= 0.8
        v.co.z *= 0.9
        if v.co.y < 0:
            v.co.z *= 0.85
    
    bmesh.update_edit_mesh(breast.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    
    return bpy.data.objects.get("Duck_Breast")

def create_head():
    bpy.ops.mesh.primitive_uv_sphere_add(segments=32, ring_count=16, radius=0.13, location=(0, 0.82, 0.72))
    head = bpy.context.active_object
    if not head:
        return None
    head.name = "Duck_Head"
    
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(head.data)
    
    for v in bm.verts:
        v.co.y *= 1.15
        v.co.z *= 1.05
        if v.co.z > 0.05:
            v.co.z += 0.02
        if v.co.y > 0.06:
            v.co.z += 0.015 * (v.co.y - 0.06) / 0.1
    
    bmesh.update_edit_mesh(head.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    
    return bpy.data.objects.get("Duck_Head")

def create_neck():
    bpy.ops.mesh.primitive_cylinder_add(vertices=24, radius=0.07, depth=0.32, location=(0, 0.6, 0.55))
    neck = bpy.context.active_object
    if not neck:
        return None
    neck.name = "Duck_Neck"
    neck.rotation_euler = (math.radians(40), 0, 0)
    bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
    
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(neck.data)
    
    for v in bm.verts:
        height_factor = (v.co.z + 0.16) / 0.32
        v.co.x *= 1.0 + 0.3 * (1 - height_factor)
        v.co.y *= 1.0 + 0.2 * (1 - height_factor)
    
    bmesh.update_edit_mesh(neck.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    
    return bpy.data.objects.get("Duck_Neck")

def create_white_collar():
    bpy.ops.mesh.primitive_torus_add(
        major_radius=0.075,
        minor_radius=0.018,
        major_segments=24,
        minor_segments=8,
        location=(0, 0.68, 0.58)
    )
    collar = bpy.context.active_object
    if not collar:
        return None
    collar.name = "Duck_Collar"
    collar.rotation_euler = (math.radians(50), 0, 0)
    collar.scale = (1.0, 1.2, 0.8)
    
    return bpy.data.objects.get("Duck_Collar")

def create_bill():
    bpy.ops.mesh.primitive_cube_add(size=0.1, location=(0, 1.0, 0.68))
    bill = bpy.context.active_object
    if not bill:
        return None
    bill.name = "Duck_Bill"
    bill.scale = (0.5, 1.2, 0.18)
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(bill.data)
    
    for v in bm.verts:
        if v.co.y > 0.03:
            v.co.x *= 1.1
            v.co.z *= 0.85
        if v.co.y > 0.05:
            v.co.z -= 0.005
        if v.co.y < -0.03:
            v.co.x *= 0.85
    
    bmesh.update_edit_mesh(bill.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    
    bpy.ops.mesh.primitive_cylinder_add(vertices=16, radius=0.022, depth=0.005, location=(0, 1.11, 0.685))
    nail = bpy.context.active_object
    if nail:
        nail.name = "Duck_Bill_Nail"
        nail.rotation_euler = (math.radians(90), 0, 0)
    
    nostrils = []
    for side in [-1, 1]:
        bpy.ops.mesh.primitive_uv_sphere_add(segments=8, ring_count=4, radius=0.008, location=(side * 0.02, 0.95, 0.7))
        nostril = bpy.context.active_object
        if nostril:
            nostril.name = f"Duck_Nostril_{side}"
            nostril.scale = (0.5, 1.2, 0.6)
            nostrils.append(nostril)
    
    return bpy.data.objects.get("Duck_Bill")

def create_eyes():
    eyes = []
    for side in [-1, 1]:
        bpy.ops.mesh.primitive_uv_sphere_add(segments=16, ring_count=8, radius=0.022, location=(side * 0.09, 0.88, 0.78))
        eye = bpy.context.active_object
        if eye:
            eye.name = f"Duck_Eye_{side}"
            eyes.append(eye)
        
        bpy.ops.mesh.primitive_uv_sphere_add(segments=12, ring_count=6, radius=0.012, location=(side * 0.1, 0.9, 0.78))
        pupil = bpy.context.active_object
        if pupil:
            pupil.name = f"Duck_Pupil_{side}"
    
    return eyes

def create_wings():
    wings = []
    for side in [-1, 1]:
        bpy.ops.mesh.primitive_cube_add(size=0.2, location=(side * 0.32, -0.05, 0.42))
        wing = bpy.context.active_object
        if not wing:
            continue
        wing.name = f"Duck_Wing_{side}"
        wing.scale = (0.12, 1.1, 0.35)
        wing.rotation_euler = (math.radians(-5), math.radians(side * 8), math.radians(side * -12))
        bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
        
        bpy.ops.object.mode_set(mode='EDIT')
        bm = bmesh.from_edit_mesh(wing.data)
        
        for v in bm.verts:
            if v.co.y < -0.05:
                v.co.x *= 0.6
                v.co.z *= 0.5
            if v.co.y > 0.05:
                v.co.z *= 0.7
            v.co.z -= abs(v.co.y) * 0.15
        
        bmesh.update_edit_mesh(wing.data)
        bpy.ops.object.mode_set(mode='OBJECT')
        
        wings.append(bpy.data.objects.get(f"Duck_Wing_{side}"))
        
        bpy.ops.mesh.primitive_cube_add(size=0.1, location=(side * 0.35, -0.25, 0.38))
        primary = bpy.context.active_object
        if primary:
            primary.name = f"Duck_Primary_{side}"
            primary.scale = (0.08, 0.6, 0.2)
            primary.rotation_euler = (math.radians(-8), math.radians(side * 5), math.radians(side * -15))
        
        bpy.ops.mesh.primitive_cube_add(size=0.08, location=(side * 0.34, -0.12, 0.40))
        speculum = bpy.context.active_object
        if speculum:
            speculum.name = f"Duck_Speculum_{side}"
            speculum.scale = (0.06, 0.35, 0.12)
    
    return wings

def create_tail():
    bpy.ops.mesh.primitive_cone_add(vertices=16, radius1=0.12, radius2=0.02, depth=0.22, location=(0, -0.62, 0.38))
    tail = bpy.context.active_object
    if not tail:
        return None
    tail.name = "Duck_Tail"
    tail.rotation_euler = (math.radians(-35), 0, 0)
    tail.scale = (1.0, 1.0, 0.4)
    bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
    
    bpy.ops.mesh.primitive_uv_sphere_add(segments=24, ring_count=12, radius=0.1, location=(0, -0.55, 0.35))
    rump = bpy.context.active_object
    if rump:
        rump.name = "Duck_Rump"
        rump.scale = (0.9, 0.8, 0.6)
    
    for i, x_off in enumerate([-0.015, 0, 0.015]):
        bpy.ops.mesh.primitive_torus_add(
            major_radius=0.025,
            minor_radius=0.006,
            major_segments=16,
            minor_segments=6,
            location=(x_off, -0.72, 0.40)
        )
        curl = bpy.context.active_object
        if curl:
            curl.name = f"Duck_Curl_{i}"
            curl.rotation_euler = (math.radians(110), math.radians(i * 5 - 5), 0)
            curl.scale = (0.7, 1.0, 0.5)
    
    for side in [-1, 1]:
        bpy.ops.mesh.primitive_cube_add(size=0.1, location=(side * 0.05, -0.68, 0.36))
        covert = bpy.context.active_object
        if covert:
            covert.name = f"Duck_Undertail_{side}"
            covert.scale = (0.4, 0.5, 0.2)
            covert.rotation_euler = (math.radians(-25), math.radians(side * 10), 0)
    
    return bpy.data.objects.get("Duck_Tail")

def create_legs():
    legs = []
    for side in [-1, 1]:
        bpy.ops.mesh.primitive_uv_sphere_add(segments=12, ring_count=6, radius=0.045, location=(side * 0.1, 0.1, 0.22))
        hip = bpy.context.active_object
        if hip:
            hip.name = f"Duck_Hip_{side}"
            hip.scale = (0.8, 1.2, 1.0)
        
        bpy.ops.mesh.primitive_cylinder_add(vertices=12, radius=0.025, depth=0.14, location=(side * 0.11, 0.08, 0.12))
        femur = bpy.context.active_object
        if femur:
            femur.name = f"Duck_Femur_{side}"
            femur.rotation_euler = (math.radians(20), 0, math.radians(side * 8))
        
        bpy.ops.mesh.primitive_cylinder_add(vertices=12, radius=0.018, depth=0.12, location=(side * 0.13, 0.1, 0.02))
        tibia = bpy.context.active_object
        if tibia:
            tibia.name = f"Duck_Tibia_{side}"
            tibia.rotation_euler = (math.radians(-15), 0, math.radians(side * 5))
        
        bpy.ops.mesh.primitive_cylinder_add(vertices=10, radius=0.012, depth=0.1, location=(side * 0.14, 0.12, -0.06))
        tarsus = bpy.context.active_object
        if tarsus:
            tarsus.name = f"Duck_Tarsus_{side}"
            tarsus.rotation_euler = (math.radians(5), 0, 0)
            legs.append(tarsus)
        
        create_foot(side)
    
    return legs

def create_foot(side):
    base_x = side * 0.14
    base_y = 0.14
    base_z = -0.12
    
    toe_data = [
        (-25, 0.11, 0.008),
        (0, 0.13, 0.009),
        (25, 0.10, 0.008),
    ]
    
    for i, (angle, length, radius) in enumerate(toe_data):
        rad = math.radians(angle)
        cx = base_x + math.sin(rad) * length * 0.4
        cy = base_y + math.cos(rad) * length * 0.4
        
        bpy.ops.mesh.primitive_cylinder_add(vertices=8, radius=radius, depth=length, location=(cx, cy, base_z))
        toe = bpy.context.active_object
        if toe:
            toe.name = f"Duck_Toe_{side}_{i}"
            toe.rotation_euler = (math.radians(88), 0, rad)
        
        for j in range(3):
            jx = base_x + math.sin(rad) * length * (0.25 + j * 0.25)
            jy = base_y + math.cos(rad) * length * (0.25 + j * 0.25)
            bpy.ops.mesh.primitive_uv_sphere_add(segments=6, ring_count=4, radius=radius * 1.1, location=(jx, jy, base_z))
            joint = bpy.context.active_object
            if joint:
                joint.name = f"Duck_Joint_{side}_{i}_{j}"
                joint.scale = (1.2, 1.2, 0.6)
    
    for i in range(2):
        angle1 = math.radians(toe_data[i][0])
        angle2 = math.radians(toe_data[i + 1][0])
        mid_angle = (angle1 + angle2) / 2
        
        bpy.ops.mesh.primitive_plane_add(size=0.1, location=(
            base_x + math.sin(mid_angle) * 0.05,
            base_y + math.cos(mid_angle) * 0.05,
            base_z - 0.003
        ))
        web = bpy.context.active_object
        if web:
            web.name = f"Duck_Web_{side}_{i}"
            web.rotation_euler = (math.radians(88), 0, mid_angle)
            web.scale = (0.6, 0.9, 1.0)
            
            bpy.ops.object.mode_set(mode='EDIT')
            bm = bmesh.from_edit_mesh(web.data)
            for v in bm.verts:
                if v.co.y > 0:
                    v.co.x *= 1.4
                if v.co.y < 0:
                    v.co.x *= 0.3
            bmesh.update_edit_mesh(web.data)
            bpy.ops.object.mode_set(mode='OBJECT')
    
    bpy.ops.mesh.primitive_cylinder_add(vertices=6, radius=0.006, depth=0.03, location=(base_x, base_y - 0.02, base_z + 0.015))
    hallux = bpy.context.active_object
    if hallux:
        hallux.name = f"Duck_Hallux_{side}"
        hallux.rotation_euler = (math.radians(120), 0, math.radians(180))

def create_materials():
    mats = {}
    
    mats['green_head'] = create_material(
        "Mallard_Green_Head",
        (0.0, 0.22, 0.08, 1.0),
        metallic=0.65,
        roughness=0.25,
        subsurface=0.1
    )
    
    mats['white_collar'] = create_material(
        "Mallard_White_Collar",
        (0.98, 0.98, 0.98, 1.0),
        roughness=0.55
    )
    
    mats['chestnut'] = create_material(
        "Mallard_Chestnut",
        (0.38, 0.1, 0.06, 1.0),
        roughness=0.5,
        subsurface=0.05
    )
    
    mats['gray_body'] = create_material(
        "Mallard_Gray_Body",
        (0.5, 0.48, 0.45, 1.0),
        roughness=0.55
    )
    
    mats['black'] = create_material(
        "Mallard_Black",
        (0.015, 0.015, 0.015, 1.0),
        roughness=0.35
    )
    
    mats['bill'] = create_material(
        "Mallard_Bill",
        (0.72, 0.65, 0.12, 1.0),
        roughness=0.45
    )
    
    mats['orange_feet'] = create_material(
        "Mallard_Orange_Feet",
        (0.85, 0.35, 0.05, 1.0),
        roughness=0.5
    )
    
    mats['speculum'] = create_material(
        "Mallard_Speculum",
        (0.08, 0.15, 0.45, 1.0),
        metallic=0.55,
        roughness=0.3
    )
    
    mats['eye_white'] = create_material(
        "Mallard_Eye_White",
        (0.9, 0.85, 0.7, 1.0),
        roughness=0.1
    )
    
    mats['eye_pupil'] = create_material(
        "Mallard_Eye_Pupil",
        (0.01, 0.01, 0.01, 1.0),
        roughness=0.05
    )
    
    mats['brown_wing'] = create_material(
        "Mallard_Brown_Wing",
        (0.28, 0.2, 0.12, 1.0),
        roughness=0.55
    )
    
    mats['nostril'] = create_material(
        "Mallard_Nostril",
        (0.08, 0.05, 0.02, 1.0),
        roughness=0.6
    )
    
    return mats

def apply_all_materials(mats):
    for obj in bpy.data.objects:
        if not obj.name.startswith("Duck_"):
            continue
        
        name = obj.name
        
        if "Head" in name:
            apply_mat_to_obj(obj, mats['green_head'])
        elif "Collar" in name:
            apply_mat_to_obj(obj, mats['white_collar'])
        elif "Neck" in name:
            apply_mat_to_obj(obj, mats['white_collar'])
        elif "Breast" in name:
            apply_mat_to_obj(obj, mats['chestnut'])
        elif "Body" in name:
            apply_mat_to_obj(obj, mats['gray_body'])
        elif "Bill" in name and "Nail" not in name:
            apply_mat_to_obj(obj, mats['bill'])
        elif "Nail" in name:
            apply_mat_to_obj(obj, mats['black'])
        elif "Nostril" in name:
            apply_mat_to_obj(obj, mats['nostril'])
        elif "Eye" in name:
            apply_mat_to_obj(obj, mats['eye_white'])
        elif "Pupil" in name:
            apply_mat_to_obj(obj, mats['eye_pupil'])
        elif "Wing" in name or "Primary" in name:
            apply_mat_to_obj(obj, mats['brown_wing'])
        elif "Speculum" in name:
            apply_mat_to_obj(obj, mats['speculum'])
        elif "Tail" in name or "Curl" in name or "Rump" in name:
            apply_mat_to_obj(obj, mats['black'])
        elif "Undertail" in name:
            apply_mat_to_obj(obj, mats['white_collar'])
        elif "Hip" in name or "Femur" in name:
            apply_mat_to_obj(obj, mats['chestnut'])
        elif any(x in name for x in ["Tibia", "Tarsus", "Toe", "Web", "Hallux", "Joint"]):
            apply_mat_to_obj(obj, mats['orange_feet'])

def smooth_all():
    for obj in bpy.data.objects:
        if obj.name.startswith("Duck_") and obj.type == 'MESH':
            obj.select_set(True)
            bpy.context.view_layer.objects.active = obj
            try:
                bpy.ops.object.shade_smooth()
            except Exception:
                pass
            obj.select_set(False)

def join_all():
    bpy.ops.object.select_all(action='DESELECT')
    
    parts = [obj for obj in bpy.data.objects if obj.name.startswith("Duck_") and obj.type == 'MESH']
    
    if not parts:
        return
    
    for obj in parts:
        obj.select_set(True)
    
    bpy.context.view_layer.objects.active = parts[0]
    bpy.ops.object.join()
    
    final = bpy.context.active_object
    if final:
        final.name = "Mallard_Duck_Anas_platyrhynchos"
        final.location = (0, 0, 0.12)

def main():
    clear_scene()
    
    create_body()
    create_breast()
    create_head()
    create_neck()
    create_white_collar()
    create_bill()
    create_eyes()
    create_wings()
    create_tail()
    create_legs()
    
    mats = create_materials()
    apply_all_materials(mats)
    
    smooth_all()
    join_all()
    
    print("Mallard duck (Anas platyrhynchos) model generated successfully.")

if __name__ == "__main__":
    main()