using System;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using ExitGames.Client.Photon;
using Photon;

public class PhotonPlayer : MonoBehaviourPunCallbacks, IPunObservable
{
    [Header("Made By ShadowApe Studios / Your Fellow Dummy \n")]
    [Header("References")]
    [Tooltip("Keep empty, or assign a head bone of a rig/armature.")]
    [SerializeField] private Transform Head; // Only put a object, or bone in here to add head rotation. Does not support prodecural animation, XR rigs, etc.
    [Tooltip("Assign a gameObject or a rig/armature in the slot.")]
    [SerializeField] private Transform Body; // Does NOT support procedural animation, XR rigs, etc., Only supports basic models, rigs (IK or not), or simple gameObjects.
    [Header("Options")]
    [Tooltip("Disables body for you, but visible for others.")]
    [SerializeField] private bool DisableBodyForLocalPlayer = false;
    [Tooltip("Do Not Touch: Smoothing factor for remote player movement and rotation. Higher values = less smoothing.")]
    [SerializeField] private float RemoteLerpSpeed = 5f;

    [Header("Cosmetics")]
    [SerializeField] private List<CosmeticSlot> CosmeticSlots = new List<CosmeticSlot>();

    // Network data
    private Quaternion netHeadRotation;
    private Vector3 netBodyPosition;
    private float netBodyYaw;

    private float bodyYOffset;

    private const float BODY_X_ROT = -90f;
    private const float BODY_Z_ROT = 0f;
    private const string COSMETIC_KEY = "Cosmetics";

    private void Awake()
    {
        DontDestroyOnLoad(gameObject);

        if (photonView.IsMine)
        {
            SetupLocalPlayer();

            if (DisableBodyForLocalPlayer && Body)
                Body.gameObject.SetActive(false);
        }

        RefreshCosmetics();
    }

    private void LateUpdate()
    {
        if (!Head) return;

        if (photonView.IsMine)
            UpdateLocal();
        else
            UpdateRemote();
    }

    // ---------------- LOCAL ----------------

    private void SetupLocalPlayer()
    {
        var mgr = PhotonManager.Instance;
        if (!mgr || !mgr.HeadTarget) return;

        if (Head && Body)
            bodyYOffset = Body.position.y - Head.position.y;

        transform.position = mgr.HeadTarget.position;
        transform.rotation = mgr.HeadTarget.rotation;
    }

    private void UpdateLocal()
    {
        var mgr = PhotonManager.Instance;
        if (!mgr || !mgr.HeadTarget) return;

        // Head follows camera rotation ONLY
        Head.rotation = mgr.HeadTarget.rotation;

        // Body follows camera position + yaw
        if (Body)
        {
            Vector3 pos = mgr.HeadTarget.position;
            pos.y += bodyYOffset;
            Body.position = pos;

            Body.rotation = Quaternion.Euler(
                BODY_X_ROT,
                mgr.HeadTarget.eulerAngles.y,
                BODY_Z_ROT
            );
        }
    }

    // ---------------- REMOTE ----------------

    private void UpdateRemote()
    {
        float t = Time.deltaTime * RemoteLerpSpeed;

        // Head rotation only
        Head.rotation = Quaternion.Slerp(
            Head.rotation,
            netHeadRotation,
            t
        );

        if (Body)
        {
            Body.position = Vector3.Lerp(
                Body.position,
                netBodyPosition,
                t
            );

            Quaternion targetRot = Quaternion.Euler(
                BODY_X_ROT,
                netBodyYaw,
                BODY_Z_ROT
            );

            Body.rotation = Quaternion.Slerp(
                Body.rotation,
                targetRot,
                t
            );
        }
    }

    // ---------------- PHOTON ----------------

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
        if (stream.IsWriting)
        {
            stream.SendNext(Head.rotation);

            if (Body)
            {
                stream.SendNext(Body.position);
                stream.SendNext(Body.eulerAngles.y);
            }
        }
        else
        {
            netHeadRotation = (Quaternion)stream.ReceiveNext();

            if (Body)
            {
                netBodyPosition = (Vector3)stream.ReceiveNext();
                netBodyYaw = (float)stream.ReceiveNext();
            }
        }
    }

    // ---------------- COSMETICS ----------------

    public void SetCosmetic(string slotName, string cosmeticName)
    {
        ApplyCosmetic(slotName, cosmeticName);

        if (!photonView.IsMine) return;

        Hashtable cosmetics = PhotonNetwork.LocalPlayer.CustomProperties.ContainsKey(COSMETIC_KEY)
            ? (Hashtable)PhotonNetwork.LocalPlayer.CustomProperties[COSMETIC_KEY]
            : new Hashtable();

        cosmetics[slotName] = cosmeticName;

        PhotonNetwork.LocalPlayer.SetCustomProperties(
            new Hashtable { { COSMETIC_KEY, cosmetics } }
        );
    }

    private void ApplyCosmetic(string slotName, string cosmeticName)
    {
        foreach (var slot in CosmeticSlots)
        {
            if (slot.SlotName != slotName) continue;

            foreach (Transform obj in slot.Objects)
                obj.gameObject.SetActive(obj.name == cosmeticName);
        }
    }

    public void RefreshCosmetics()
    {
        if (!photonView.Owner.CustomProperties.ContainsKey(COSMETIC_KEY))
            return;

        Hashtable cosmetics = (Hashtable)photonView.Owner.CustomProperties[COSMETIC_KEY];

        foreach (var key in cosmetics.Keys)
        {
            string slotName = key as string;
            string cosmeticName = cosmetics[key] as string;

            if (!string.IsNullOrEmpty(slotName) && !string.IsNullOrEmpty(cosmeticName))
                ApplyCosmetic(slotName, cosmeticName);
        }
    }

    public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
    {
        if (targetPlayer != photonView.Owner) return;

        if (changedProps.ContainsKey(COSMETIC_KEY))
            RefreshCosmetics();
    }

    // ---------------- DATA ----------------

    [Serializable]
    public class CosmeticSlot
    {
        public string SlotName;
        public Transform[] Objects;
    }
}
