using UnityEngine;
using Unity.Cinemachine;
using System.Collections.Generic;
using UnityRawInput;
using System.Linq;

public class CameraManager : MonoBehaviour
{
    #region Classes
    private static class KeyMapping
    {
        private static readonly Dictionary<KeyCode, RawKey> _mapping;

        static KeyMapping()
        {
            _mapping = new Dictionary<KeyCode, RawKey>(RawKeySetup.KeyMapping);
        }

        public static bool TryGetRawKey(KeyCode keyCode, out RawKey rawKey)
        {
            return _mapping.TryGetValue(keyCode, out rawKey);
        }

        public static bool TryGetKeyCode(RawKey rawKey, out KeyCode keyCode)
        {
            var pair = _mapping.FirstOrDefault(x => x.Value == rawKey);
            keyCode = pair.Key;
            return keyCode != KeyCode.None;
        }

        public static bool IsValidRawKey(RawKey key)
        {
            return _mapping.ContainsValue(key);
        }
    }

    [System.Serializable]
    public class HotkeyCommand
    {
        public List<RawKey> rawKeys = new List<RawKey>();
        [System.NonSerialized] private List<KeyCode> unityKeys = new List<KeyCode>();
        [System.NonSerialized] public bool isRecording = false;
        [System.NonSerialized] private float recordStartTime;
        [System.NonSerialized] private const float MAX_RECORD_TIME = 2f;

        public void StartRecording()
        {
            isRecording = true;
            recordStartTime = Time.time;
            rawKeys.Clear();
        }

        public void StopRecording()
        {
            isRecording = false;
            InitializeUnityKeys();
        }

        public void UpdateRecording()
        {
            if (!isRecording) return;

            if (Time.time - recordStartTime > MAX_RECORD_TIME)
            {
                StopRecording();
                return;
            }

            // 현재 눌린 키 확인
            foreach (KeyCode keyCode in System.Enum.GetValues(typeof(KeyCode)))
            {
                if (Input.GetKeyDown(keyCode) && KeyMapping.TryGetRawKey(keyCode, out RawKey rawKey))
                {
                    if (!rawKeys.Contains(rawKey))
                    {
                        rawKeys.Add(rawKey);
                    }
                }
            }

            // 모든 키가 떼어졌는지 확인
            bool allKeysReleased = rawKeys.Any() && rawKeys.All(key => !Input.GetKey(KeyMapping.TryGetKeyCode(key, out KeyCode keyCode) ? keyCode : KeyCode.None));

            if (allKeysReleased)
            {
                StopRecording();
            }
        }

        public void InitializeUnityKeys()
        {
            try
            {
                unityKeys ??= new List<KeyCode>();
                unityKeys.Clear();
                
                if (rawKeys == null || !rawKeys.Any()) return;
                
                foreach (var rawKey in rawKeys)
                {
                    if (KeyMapping.TryGetKeyCode(rawKey, out KeyCode keyCode) && keyCode != KeyCode.None)
                    {
                        unityKeys.Add(keyCode);
                    }
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError($"핫키 초기화 중 오류 발생: {e.Message}");
            }
        }

        public bool IsTriggered()
        {
            if (isRecording) return false;

            try
            {
                if (rawKeys == null || !rawKeys.Any()) return false;

                // RawInput으로 체크 (우선순위)
                bool allRawKeysPressed = rawKeys.All(key => RawInput.IsKeyDown(key));
                if (allRawKeysPressed)
                {
                    return true;
                }

                // Unity Input으로 체크 (백업)
                if (unityKeys != null && unityKeys.Any())
                {
                    bool allKeysPressed = unityKeys.All(key => Input.GetKey(key));
                    if (allKeysPressed)
                    {
                        return true;
                    }
                }

                return false;
            }
            catch (System.Exception e)
            {
                Debug.LogError($"핫키 트리거 확인 중 오류 발생: {e.Message}");
                return false;
            }
        }

        public override string ToString() => 
            rawKeys?.Any() == true ? string.Join(" + ", rawKeys) : "설정되지 않음";
    }

    [System.Serializable]
    public class CameraPreset
    {
        public string presetName = "New Camera Preset";
        public CinemachineCamera virtualCamera;
        public HotkeyCommand hotkey;
        [System.NonSerialized] public bool isEditingHotkey = false;

        public CameraPreset(CinemachineCamera camera)
        {
            virtualCamera = camera;
            presetName = camera?.gameObject.name ?? "Unnamed Camera";
            hotkey = new HotkeyCommand();
        }

        public bool IsValid() => virtualCamera != null && hotkey != null;
    }
    #endregion

    #region Events
    public delegate void CameraChangedEventHandler(CameraPreset oldPreset, CameraPreset newPreset);
    public event CameraChangedEventHandler OnCameraChanged;
    #endregion

    #region Fields
    [SerializeField] public List<CameraPreset> cameraPresets = new List<CameraPreset>();
    
    [Header("Camera Control Settings")]
    [SerializeField, Range(0.1f, 10f)] private float rotationSensitivity = 2f;
    [SerializeField, Range(0.1f, 20f)] private float panSpeed = 10f;
    [SerializeField, Range(0.1f, 20f)] private float zoomSpeed = 10f;
    [SerializeField, Range(0.1f, 10f)] private float orbitSpeed = 5f;
    
    private CinemachineCamera currentCamera;
    private InputHandler inputHandler;
    private Vector3 targetPosition;
    private CameraPreset currentPreset;
    #endregion

    #region Properties
    private bool IsValidSetup => currentCamera != null && inputHandler != null;
    public CameraPreset CurrentPreset => currentPreset;
    #endregion

    #region Unity Messages
    private void Awake()
    {
        try
        {
            InitializeInputHandler();
            InitializeRawInput();
            InitializeCameraPresets();
        }
        catch (System.Exception e)
        {
            Debug.LogError($"초기화 중 오류 발생: {e.Message}");
        }
    }

    private void OnDestroy()
    {
        try
        {
            CleanupRawInput();
        }
        catch (System.Exception e)
        {
            Debug.LogError($"정리 중 오류 발생: {e.Message}");
        }
    }

    private void Update()
    {
        if (!IsValidSetup) return;
        
        try
        {
            UpdateHotkeyRecording();
            HandleCameraControls();
            HandleUnityHotkeys();
        }
        catch (System.Exception e)
        {
            Debug.LogError($"카메라 제어 중 오류 발생: {e.Message}");
        }
    }

    private void UpdateHotkeyRecording()
    {
        foreach (var preset in cameraPresets)
        {
            if (preset?.hotkey?.isRecording == true)
            {
                preset.hotkey.UpdateRecording();
            }
        }
    }
    #endregion

    #region Initialization
    private void InitializeInputHandler()
    {
        inputHandler = GetComponent<InputHandler>();
        if (inputHandler == null)
        {
            inputHandler = gameObject.AddComponent<InputHandler>();
            Debug.Log("InputHandler 컴포넌트가 자동으로 추가되었습니다.");
        }
    }

    private void InitializeRawInput()
    {
        if (!RawInput.IsRunning)
        {
            RawInput.Start();
            RawInput.WorkInBackground = true;
        }
        RawInput.OnKeyDown += HandleKeyDown;
    }

    private void InitializeCameraPresets()
    {
        if (cameraPresets == null)
        {
            cameraPresets = new List<CameraPreset>();
            Debug.LogWarning("카메라 프리셋 리스트가 null이어서 새로 생성되었습니다.");
        }

        if (!cameraPresets.Any())
        {
            Debug.LogWarning("카메라 프리셋이 설정되어 있지 않습니다.");
            return;
        }

        foreach (var preset in cameraPresets.Where(p => p?.hotkey != null))
        {
            preset.hotkey.InitializeUnityKeys();
        }
        
        SwitchToCamera(0);
    }

    private void CleanupRawInput()
    {
        if (RawInput.IsRunning)
        {
            RawInput.OnKeyDown -= HandleKeyDown;
            RawInput.Stop();
        }
    }
    #endregion

    #region Input Handling
    private void HandleKeyDown(RawKey key)
    {
        if (key == default(RawKey)) return;
        TryActivatePresetByInput(preset => 
        {
            if (preset?.hotkey == null) return false;
            bool contains = preset.hotkey.rawKeys?.Contains(key) == true;
            bool triggered = preset.hotkey.IsTriggered();
            return contains && triggered;
        });
    }

    private void HandleUnityHotkeys()
    {
        if (!Input.anyKeyDown) return;
        TryActivatePresetByInput(preset => 
        {
            if (preset?.hotkey == null) return false;
            return preset.hotkey.IsTriggered();
        });
    }

    private void TryActivatePresetByInput(System.Func<CameraPreset, bool> predicate)
    {
        try
        {
            var matchingPreset = cameraPresets?
                .FirstOrDefault(predicate);
                
            if (matchingPreset != null)
            {
                SwitchToCamera(cameraPresets.IndexOf(matchingPreset));
            }
        }
        catch (System.Exception e)
        {
            Debug.LogError($"프리셋 활성화 중 오류 발생: {e.Message}");
        }
    }
    #endregion

    #region Camera Controls
    private void HandleCameraControls()
    {
        if (!IsValidSetup) return;

        Transform cameraTransform = currentCamera.transform;
        if (cameraTransform == null) return;

        HandleRotation(cameraTransform);
        HandlePanning(cameraTransform);
        HandleZooming(cameraTransform);
        HandleOrbiting(cameraTransform);
    }

    private void HandleRotation(Transform cameraTransform)
    {
        if (!inputHandler.IsRightMouseHeld()) return;

        Vector2 lookDelta = inputHandler.GetLookDelta();
        if (lookDelta.sqrMagnitude < float.Epsilon) return;

        var rotation = new Vector3(-lookDelta.y, lookDelta.x, 0f) * rotationSensitivity;
        cameraTransform.rotation *= Quaternion.Euler(rotation);
    }

    private void HandlePanning(Transform cameraTransform)
    {
        if (!inputHandler.IsMiddleMouseHeld()) return;

        Vector2 panDelta = inputHandler.GetLookDelta();
        if (panDelta.sqrMagnitude < float.Epsilon) return;

        Vector3 panMovement = 
            cameraTransform.right * -panDelta.x * panSpeed +
            cameraTransform.up * -panDelta.y * panSpeed;
        cameraTransform.position += panMovement * Time.deltaTime;
    }

    private void HandleZooming(Transform cameraTransform)
    {
        float zoomDelta = inputHandler.GetZoomDelta();
        if (Mathf.Abs(zoomDelta) <= 0.1f) return;

        cameraTransform.position += cameraTransform.forward * zoomDelta * zoomSpeed * Time.deltaTime;
    }

    private void HandleOrbiting(Transform cameraTransform)
    {
        if (!inputHandler.IsOrbitActive()) return;

        if (inputHandler.IsOrbitStarting())
        {
            UpdateOrbitTarget(cameraTransform);
        }

        PerformOrbitRotation(cameraTransform);
    }

    private void UpdateOrbitTarget(Transform cameraTransform)
    {
        if (Camera.main == null)
        {
            targetPosition = cameraTransform.position + cameraTransform.forward * 10f;
            return;
        }

        Ray ray = Camera.main.ScreenPointToRay(inputHandler.GetMousePosition());
        targetPosition = Physics.Raycast(ray, out RaycastHit hit) 
            ? hit.point 
            : cameraTransform.position + cameraTransform.forward * 10f;
    }

    private void PerformOrbitRotation(Transform cameraTransform)
    {
        Vector2 orbitDelta = inputHandler.GetLookDelta();
        if (orbitDelta.sqrMagnitude < float.Epsilon) return;

        Vector3 directionToTarget = targetPosition - cameraTransform.position;
        float distance = directionToTarget.magnitude;

        // 수평 회전
        cameraTransform.RotateAround(targetPosition, Vector3.up, orbitDelta.x * orbitSpeed);
        
        // 수직 회전 제한 추가
        Vector3 right = Vector3.Cross(Vector3.up, directionToTarget.normalized);
        float currentAngle = Vector3.Angle(Vector3.up, directionToTarget);
        float newAngle = currentAngle - orbitDelta.y * orbitSpeed;
        
        if (newAngle > 5f && newAngle < 175f)
        {
            cameraTransform.RotateAround(targetPosition, right, -orbitDelta.y * orbitSpeed);
        }
        
        // 거리 유지
        cameraTransform.position = targetPosition - cameraTransform.forward * distance;
    }
    #endregion

    #region Camera Management
    public void SwitchToCamera(int index)
    {
        if (!IsValidCameraIndex(index)) return;

        var newPreset = cameraPresets[index];
        if (!newPreset.IsValid())
        {
            Debug.LogWarning($"프리셋 {index}의 카메라가 설정되지 않았습니다.");
            return;
        }

        var oldPreset = currentPreset;
        currentPreset = newPreset;
        UpdateCameraPriorities(newPreset.virtualCamera);
        
        try
        {
            OnCameraChanged?.Invoke(oldPreset, newPreset);
        }
        catch (System.Exception e)
        {
            Debug.LogError($"카메라 변경 이벤트 처리 중 오류 발생: {e.Message}");
        }

        Debug.Log($"카메라 전환: {newPreset.presetName}");
    }

    private bool IsValidCameraIndex(int index) =>
        cameraPresets != null && index >= 0 && index < cameraPresets.Count;

    private void UpdateCameraPriorities(CinemachineCamera newCamera)
    {
        if (newCamera == null)
        {
            Debug.LogError("새로운 카메라가 null입니다.");
            return;
        }

        if (currentCamera != null)
        {
            currentCamera.Priority = 0;
        }
        
        currentCamera = newCamera;
        currentCamera.Priority = 10;
    }
    #endregion

    #region Hotkey Management
    public void StartRecordingHotkey(int presetIndex)
    {
        if (!IsValidCameraIndex(presetIndex)) return;

        var preset = cameraPresets[presetIndex];
        if (!preset.IsValid())
        {
            Debug.LogWarning($"프리셋 {presetIndex}가 유효하지 않습니다.");
            return;
        }

        // 다른 프리셋의 레코딩 중지
        foreach (var otherPreset in cameraPresets)
        {
            if (otherPreset?.hotkey?.isRecording == true)
            {
                otherPreset.hotkey.StopRecording();
            }
        }

        preset.hotkey.StartRecording();
    }

    public void StopRecordingHotkey(int presetIndex)
    {
        if (!IsValidCameraIndex(presetIndex)) return;

        var preset = cameraPresets[presetIndex];
        if (preset?.hotkey?.isRecording == true)
        {
            preset.hotkey.StopRecording();
        }
    }
    #endregion
}