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

# ---------------------------------------------------------------------------
# Utility helpers
# ---------------------------------------------------------------------------

def make_material(name, base_color, roughness=0.6, metallic=0.0):
    """Create a simple Principled BSDF material, defensively."""
    mat = bpy.data.materials.get(name)
    if mat is None:
        mat = bpy.data.materials.new(name)
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    bsdf = nodes.get("Principled BSDF")
    if bsdf is None:
        for n in nodes:
            if n.type == 'BSDF_PRINCIPLED':
                bsdf = n
                break
    if bsdf is None:
        return mat

    def set_socket(sockname, value):
        try:
            sock = bsdf.inputs.get(sockname)
            if sock is not None:
                sock.default_value = value
        except Exception:
            pass

    set_socket("Base Color", base_color)
    set_socket("Roughness", roughness)
    set_socket("Metallic", metallic)
    # Optional tweaks guarded (socket names changed across versions)
    try:
        set_socket("Specular IOR Level", 0.4)
    except Exception:
        pass
    try:
        set_socket("Subsurface Weight", 0.0)
    except Exception:
        pass
    return mat


def assign_material(obj, mat):
    if obj is None or obj.data is None or mat is None:
        return
    if len(obj.data.materials) == 0:
        obj.data.materials.append(mat)
    else:
        obj.data.materials[0] = mat


def shade_smooth(obj):
    if obj is None or obj.data is None:
        return
    for p in obj.data.polygons:
        p.use_smooth = True


def add_uv_sphere(radius=1.0, location=(0, 0, 0), segments=24, rings=16, name="Sphere"):
    bpy.ops.mesh.primitive_uv_sphere_add(radius=radius, location=location,
                                          segments=segments, ring_count=rings)
    obj = bpy.context.active_object
    if obj is not None:
        obj.name = name
    return obj


def add_cylinder(radius=1.0, depth=1.0, location=(0, 0, 0), verts=20, name="Cylinder"):
    bpy.ops.mesh.primitive_cylinder_add(radius=radius, depth=depth,
                                         location=location, vertices=verts)
    obj = bpy.context.active_object
    if obj is not None:
        obj.name = name
    return obj


def add_cone(radius1=1.0, radius2=0.0, depth=1.0, location=(0, 0, 0),
             verts=20, name="Cone"):
    bpy.ops.mesh.primitive_cone_add(radius1=radius1, radius2=radius2, depth=depth,
                                    location=location, vertices=verts)
    obj = bpy.context.active_object
    if obj is not None:
        obj.name = name
    return obj


def set_transform(obj, location=None, rotation_euler=None, scale=None):
    if obj is None:
        return
    if location is not None:
        obj.location = location
    if rotation_euler is not None:
        obj.rotation_euler = rotation_euler
    if scale is not None:
        obj.scale = scale


def apply_transform(obj):
    if obj is None:
        return
    bpy.ops.object.select_all(action='DESELECT')
    obj.select_set(True)
    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)


# ---------------------------------------------------------------------------
# Materials
# ---------------------------------------------------------------------------

feather_white = make_material("StorkWhite", (0.90, 0.90, 0.88, 1.0), roughness=0.55)
feather_black = make_material("StorkBlack", (0.02, 0.02, 0.02, 1.0), roughness=0.5)
beak_red = make_material("StorkBeak", (0.72, 0.06, 0.03, 1.0), roughness=0.35)
leg_red = make_material("StorkLeg", (0.70, 0.10, 0.08, 1.0), roughness=0.4)
skin_red = make_material("StorkSkin", (0.55, 0.05, 0.04, 1.0), roughness=0.45)
eye_dark = make_material("StorkEye", (0.02, 0.02, 0.03, 1.0), roughness=0.15)
eye_white = make_material("StorkEyeRing", (0.10, 0.10, 0.10, 1.0), roughness=0.3)

created = []

# ---------------------------------------------------------------------------
# BODY  (torso) - elongated ellipsoid
# ---------------------------------------------------------------------------

body = add_uv_sphere(radius=0.5, location=(0.0, 0.0, 0.0), segments=32, rings=20,
                     name="Body")
set_transform(body, scale=(1.6, 0.85, 0.85))
apply_transform(body)
assign_material(body, feather_white)
shade_smooth(body)
created.append(body)

# ---------------------------------------------------------------------------
# NECK  - curved S-shape approximated with tapered cylinders
# ---------------------------------------------------------------------------

# Neck base near front-top of body, rising and curving forward
neck_points = [
    (0.75, 0.0, 0.30, 0.22),
    (0.95, 0.0, 0.65, 0.18),
    (1.05, 0.0, 1.05, 0.15),
    (1.05, 0.0, 1.45, 0.13),
    (1.10, 0.0, 1.80, 0.12),
]
neck_objs = []
for i in range(len(neck_points) - 1):
    x0, y0, z0, r0 = neck_points[i]
    x1, y1, z1, r1 = neck_points[i + 1]
    mid = ((x0 + x1) / 2.0, (y0 + y1) / 2.0, (z0 + z1) / 2.0)
    vec = Vector((x1 - x0, y1 - y0, z1 - z0))
    length = vec.length
    seg = add_cone(radius1=r0, radius2=r1, depth=length, location=mid,
                   verts=20, name="NeckSeg_%d" % i)
    # orient cylinder along vec (default along +Z)
    if length > 1e-6:
        z_axis = Vector((0, 0, 1))
        axis = z_axis.cross(vec.normalized())
        angle = math.acos(max(-1.0, min(1.0, z_axis.dot(vec.normalized()))))
        if axis.length > 1e-6:
            seg.rotation_mode = 'AXIS_ANGLE'
            seg.rotation_axis_angle = (angle, axis.x, axis.y, axis.z)
    assign_material(seg, feather_white)
    shade_smooth(seg)
    neck_objs.append(seg)
    created.append(seg)

# ---------------------------------------------------------------------------
# HEAD
# ---------------------------------------------------------------------------

head_loc = (1.18, 0.0, 1.92)
head = add_uv_sphere(radius=0.16, location=head_loc, segments=24, rings=16,
                     name="Head")
set_transform(head, scale=(1.3, 0.95, 1.0))
apply_transform(head)
assign_material(head, feather_white)
shade_smooth(head)
created.append(head)

# ---------------------------------------------------------------------------
# BEAK  - long straight pointed red beak
# ---------------------------------------------------------------------------

beak_len = 0.75
beak = add_cone(radius1=0.09, radius2=0.012, depth=beak_len,
                location=(head_loc[0] + 0.10 + beak_len / 2.0, 0.0, head_loc[2] - 0.02),
                verts=20, name="Beak")
set_transform(beak, rotation_euler=(0.0, math.radians(90.0), 0.0))
apply_transform(beak)
assign_material(beak, beak_red)
shade_smooth(beak)
created.append(beak)

# small lower mandible line via a thin second cone for realism
lower_beak = add_cone(radius1=0.055, radius2=0.01, depth=beak_len * 0.95,
                      location=(head_loc[0] + 0.10 + beak_len * 0.95 / 2.0, 0.0,
                                head_loc[2] - 0.055),
                      verts=16, name="LowerBeak")
set_transform(lower_beak, rotation_euler=(0.0, math.radians(90.0), 0.0))
apply_transform(lower_beak)
assign_material(lower_beak, beak_red)
shade_smooth(lower_beak)
created.append(lower_beak)

# ---------------------------------------------------------------------------
# EYES  (both sides)
# ---------------------------------------------------------------------------

for side in (-1, 1):
    eye = add_uv_sphere(radius=0.035,
                        location=(head_loc[0] + 0.10, side * 0.11, head_loc[2] + 0.03),
                        segments=16, rings=12, name="Eye_%d" % side)
    assign_material(eye, eye_dark)
    shade_smooth(eye)
    created.append(eye)
    # bare skin patch around eye (dark)
    patch = add_uv_sphere(radius=0.05,
                          location=(head_loc[0] + 0.09, side * 0.105, head_loc[2] + 0.02),
                          segments=14, rings=10, name="EyePatch_%d" % side)
    set_transform(patch, scale=(1.0, 0.6, 1.0))
    apply_transform(patch)
    assign_material(patch, eye_white)
    shade_smooth(patch)
    created.append(patch)

# ---------------------------------------------------------------------------
# TAIL  - short white tail with black flight feather ends folded over
# ---------------------------------------------------------------------------

tail = add_cone(radius1=0.30, radius2=0.05, depth=0.65,
                location=(-1.05, 0.0, 0.05), verts=20, name="Tail")
set_transform(tail, rotation_euler=(0.0, math.radians(-95.0), 0.0),
              scale=(1.0, 1.6, 0.5))
apply_transform(tail)
assign_material(tail, feather_white)
shade_smooth(tail)
created.append(tail)

# ---------------------------------------------------------------------------
# WINGS  - folded along body, white with black rear/tips
# ---------------------------------------------------------------------------

def build_wing(side):
    # main wing coverts as a flattened folded shape
    wing = add_uv_sphere(radius=0.45,
                         location=(-0.15, side * 0.55, 0.15),
                         segments=24, rings=16, name="Wing_%d" % side)
    set_transform(wing, scale=(1.7, 0.35, 0.75))
    apply_transform(wing)
    assign_material(wing, feather_white)
    shade_smooth(wing)
    created.append(wing)

    # black primary/secondary feathers at rear portion of folded wing
    primaries = add_cone(radius1=0.28, radius2=0.02, depth=1.0,
                         location=(-0.95, side * 0.52, 0.0),
                         verts=18, name="WingPrimaries_%d" % side)
    set_transform(primaries, rotation_euler=(0.0, math.radians(-95.0), 0.0),
                  scale=(1.0, 0.35, 0.6))
    apply_transform(primaries)
    assign_material(primaries, feather_black)
    shade_smooth(primaries)
    created.append(primaries)

build_wing(-1)
build_wing(1)

# ---------------------------------------------------------------------------
# LEGS  - long red legs, thigh + tarsus + foot with toes
# ---------------------------------------------------------------------------

def build_leg(side):
    hip_x = -0.15
    hip_y = side * 0.22
    hip_z = -0.30

    # upper leg (thigh) - short, angled forward
    thigh_top = Vector((hip_x, hip_y, hip_z))
    thigh_bot = Vector((hip_x + 0.10, hip_y, hip_z - 0.45))
    mid = (thigh_top + thigh_bot) / 2.0
    vec = thigh_bot - thigh_top
    thigh = add_cone(radius1=0.09, radius2=0.06, depth=vec.length,
                     location=tuple(mid), verts=14, name="Thigh_%d" % side)
    zx = Vector((0, 0, 1))
    if vec.length > 1e-6:
        axis = zx.cross(vec.normalized())
        ang = math.acos(max(-1.0, min(1.0, zx.dot(vec.normalized()))))
        if axis.length > 1e-6:
            thigh.rotation_mode = 'AXIS_ANGLE'
            thigh.rotation_axis_angle = (ang, axis.x, axis.y, axis.z)
    assign_material(thigh, leg_red)
    shade_smooth(thigh)
    created.append(thigh)

    # tarsus (long lower leg) straight down to ground
    tarsus_top = thigh_bot
    tarsus_bot = Vector((thigh_bot.x - 0.02, hip_y, -1.55))
    midt = (tarsus_top + tarsus_bot) / 2.0
    vect = tarsus_bot - tarsus_top
    tarsus = add_cone(radius1=0.05, radius2=0.035, depth=vect.length,
                      location=tuple(midt), verts=12, name="Tarsus_%d" % side)
    if vect.length > 1e-6:
        axis = zx.cross(vect.normalized())
        ang = math.acos(max(-1.0, min(1.0, zx.dot(vect.normalized()))))
        if axis.length > 1e-6:
            tarsus.rotation_mode = 'AXIS_ANGLE'
            tarsus.rotation_axis_angle = (ang, axis.x, axis.y, axis.z)
    assign_material(tarsus, leg_red)
    shade_smooth(tarsus)
    created.append(tarsus)

    foot_base = tarsus_bot

    # toes: 3 forward + 1 back
    toe_dirs = [
        (0.28, 0.10, -0.05),
        (0.30, 0.0, -0.05),
        (0.28, -0.10, -0.05),
        (-0.14, 0.0, -0.04),  # hind toe
    ]
    for ti, (dx, dy, dz) in enumerate(toe_dirs):
        end = foot_base + Vector((dx, dy, dz))
        midf = (foot_base + end) / 2.0
        vecf = end - foot_base
        toe = add_cone(radius1=0.028, radius2=0.008, depth=vecf.length,
                       location=tuple(midf), verts=8,
                       name="Toe_%d_%d" % (side, ti))
        if vecf.length > 1e-6:
            axis = zx.cross(vecf.normalized())
            ang = math.acos(max(-1.0, min(1.0, zx.dot(vecf.normalized()))))
            if axis.length > 1e-6:
                toe.rotation_mode = 'AXIS_ANGLE'
                toe.rotation_axis_angle = (ang, axis.x, axis.y, axis.z)
        assign_material(toe, leg_red)
        shade_smooth(toe)
        created.append(toe)

build_leg(-1)
build_leg(1)

# ---------------------------------------------------------------------------
# Join everything into a single object named "WhiteStork"
# ---------------------------------------------------------------------------

bpy.ops.object.select_all(action='DESELECT')
valid = [o for o in created if o is not None and o.name in bpy.data.objects]
if valid:
    for o in valid:
        o.select_set(True)
    bpy.context.view_layer.objects.active = valid[0]
    try:
        bpy.ops.object.join()
    except Exception:
        pass
    stork = bpy.context.active_object
    if stork is not None:
        stork.name = "WhiteStork_Ciconia_ciconia"