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

public class CameraManager : MonoBehaviour, IController
{
    #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()
        {
            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);
                }
            }
        }

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

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

            bool allRawKeysPressed = rawKeys.All(key => RawInput.IsKeyDown(key));
            if (allRawKeysPressed) return true;

            if (unityKeys.Any())
            {
                return unityKeys.All(key => Input.GetKey(key));
            }

            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")]
    private float rotationSensitivity = 2f;
    private float panSpeed = 0.02f;
    private float zoomSpeed = 0.1f;
    private float orbitSpeed = 10f;
    
    private CinemachineCamera currentCamera;
    private InputHandler inputHandler;
    private CameraPreset currentPreset;
    private Vector3 rotationCenter = Vector3.zero;
    private Vector3 rotationStartPosition;
    private bool isRotating = false;
    
    // 초기 카메라 상태 저장
    private Vector3 initialPosition;
    private Quaternion initialRotation;
    private bool isInitialStateSet = false;
    #endregion

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

    #region Unity Messages
    private void Awake()
    {
        InitializeInputHandler();
        InitializeRawInput();
        InitializeCameraPresets();
    }

    private void OnDestroy()
    {
        if (RawInput.IsRunning)
        {
            RawInput.OnKeyDown -= HandleRawKeyDown;
            RawInput.Stop();
        }
    }

    private void Update()
    {
        if (!IsValidSetup) return;
        
        UpdateHotkeyRecording();
        HandleCameraControls();
        HandleHotkeys();
    }

    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>();
        }
    }

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

    private void InitializeCameraPresets()
    {
        if (cameraPresets == null)
        {
            cameraPresets = new List<CameraPreset>();
        }

        if (!cameraPresets.Any())
        {
            return;
        }

        foreach (var preset in cameraPresets.Where(p => p?.hotkey != null))
        {
            preset.hotkey.InitializeUnityKeys();
        }
        
        Set(0);
        
        // 초기 카메라 상태 저장
        if (currentCamera != null && !isInitialStateSet)
        {
            initialPosition = currentCamera.transform.position;
            initialRotation = currentCamera.transform.rotation;
            isInitialStateSet = true;
        }
    }
    #endregion

    #region Input Handling
    private void HandleRawKeyDown(RawKey key)
    {
        if (key == default(RawKey)) return;
        TryActivatePresetByInput(preset => 
        {
            if (preset?.hotkey == null) return false;
            return preset.hotkey.IsTriggered();
        });
    }

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

    private void TryActivatePresetByInput(System.Func<CameraPreset, bool> predicate)
    {
        var matchingPreset = cameraPresets?.FirstOrDefault(predicate);
        if (matchingPreset != null)
        {
            Set(cameraPresets.IndexOf(matchingPreset));
        }
    }
    #endregion

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

        var virtualCamera = currentCamera;
        if (virtualCamera == null) return;

        // Alt+Q로 초기 위치로 복원
        if (Input.GetKey(KeyCode.LeftAlt) && Input.GetKeyDown(KeyCode.Q))
        {
            RestoreInitialCameraState();
            return;
        }

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

    private void HandleRotation(CinemachineCamera virtualCamera)
    {
        Transform cameraTransform = virtualCamera.transform;
        
        if (inputHandler.IsRightMouseHeld())
        {
            if (!isRotating)
            {
                // 회전 시작 시 현재 위치 저장
                isRotating = true;
                rotationStartPosition = cameraTransform.position;
                // 현재 카메라의 y값을 기준으로 회전 중심점 설정
                rotationCenter = new Vector3(0f, rotationStartPosition.y, 0f);
            }

            Vector2 lookDelta = inputHandler.GetLookDelta();
            if (lookDelta.sqrMagnitude < float.Epsilon) return;
            
            // 현재 회전값을 오일러 각도로 가져오기
            Vector3 currentEuler = cameraTransform.eulerAngles;
            
            // X축 회전값을 -80도에서 80도 사이로 제한하기 위해 360도 형식에서 변환
            float currentX = currentEuler.x;
            if (currentX > 180f) currentX -= 360f;
            
            // 새로운 회전값 계산
            float newX = currentX - lookDelta.y * rotationSensitivity;
            float newY = currentEuler.y + lookDelta.x * rotationSensitivity;
            
            // X축 회전 제한 (-80도 ~ 80도)
            newX = Mathf.Clamp(newX, -80f, 80f);
            
            // 회전 적용
            Quaternion targetRotation = Quaternion.Euler(newX, newY, 0f);
            cameraTransform.rotation = targetRotation;
            
            // 회전 시작 위치를 기준으로 회전 (y값 유지)
            Vector3 relativePosition = rotationStartPosition - rotationCenter;
            Vector3 rotatedPosition = targetRotation * new Vector3(relativePosition.x, 0f, relativePosition.z);
            Vector3 newPosition = rotationCenter + rotatedPosition;
            cameraTransform.position = newPosition;
        }
        else
        {
            isRotating = false;
        }
    }

    private void HandlePanning(CinemachineCamera virtualCamera)
    {
        if (!inputHandler.IsMiddleMouseHeld()) return;

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

        Transform cameraTransform = virtualCamera.transform;
        
        // 이동 적용 (카메라의 right와 up 방향으로 이동)
        Vector3 right = cameraTransform.right * -panDelta.x * panSpeed;
        Vector3 up = cameraTransform.up * -panDelta.y * panSpeed;
        
        cameraTransform.position += right + up;
    }

    private void HandleZooming(CinemachineCamera virtualCamera)
    {
        if (inputHandler.IsZoomActive())
        {
            // Ctrl + 좌클릭으로 줌
            Vector2 lookDelta = inputHandler.GetLookDelta();
            if (lookDelta.sqrMagnitude < float.Epsilon) return;

            Transform cameraTransform = virtualCamera.transform;
            Vector3 forward = cameraTransform.forward * lookDelta.y * zoomSpeed * 10f;
            cameraTransform.position += forward;
        }
        else
        {
            // 마우스 휠로 줌
            float zoomDelta = inputHandler.GetZoomDelta();
            if (Mathf.Abs(zoomDelta) <= 0.1f) return;

            Transform cameraTransform = virtualCamera.transform;
            Vector3 forward = cameraTransform.forward * zoomDelta * zoomSpeed;
            cameraTransform.position += forward;
        }
    }

    private void HandleOrbiting(CinemachineCamera virtualCamera)
    {
        if (!inputHandler.IsOrbitActive()) return;

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

        Transform cameraTransform = virtualCamera.transform;
        
        // 현재 회전값을 오일러 각도로 가져오기
        Vector3 currentEuler = cameraTransform.eulerAngles;
        
        // X축 회전값을 -80도에서 80도 사이로 제한하기 위해 360도 형식에서 변환
        float currentX = currentEuler.x;
        if (currentX > 180f) currentX -= 360f;
        
        // 새로운 회전값 계산
        float newX = currentX - orbitDelta.y * orbitSpeed;
        float newY = currentEuler.y + orbitDelta.x * orbitSpeed;
        
        // X축 회전 제한 (-80도 ~ 80도)
        newX = Mathf.Clamp(newX, -80f, 80f);
        
        // 회전 적용
        Quaternion targetRotation = Quaternion.Euler(newX, newY, 0f);
        cameraTransform.rotation = targetRotation;
        
        // 원점으로부터의 거리 유지
        float distance = cameraTransform.position.magnitude;
        
        // 새로운 위치 계산 (원점으로부터의 거리 유지)
        Vector3 newPosition = targetRotation * Vector3.back * distance;
        cameraTransform.position = newPosition;
    }

    private void RestoreInitialCameraState()
    {
        if (!isInitialStateSet || currentCamera == null) return;

        currentCamera.transform.position = initialPosition;
        currentCamera.transform.rotation = initialRotation;
        
        // 회전 중심점 초기화
        rotationCenter = new Vector3(0f, initialPosition.y, 0f);
    }
    #endregion

    #region Camera Management
    public void Set(int index)
    {
        if (cameraPresets == null || index < 0 || index >= cameraPresets.Count) return;

        var newPreset = cameraPresets[index];
        if (!newPreset.IsValid()) return;

        var oldPreset = currentPreset;
        currentPreset = newPreset;
        UpdateCameraPriorities(newPreset.virtualCamera);
        
        OnCameraChanged?.Invoke(oldPreset, newPreset);
    }

    private void UpdateCameraPriorities(CinemachineCamera newCamera)
    {
        if (newCamera == null) return;

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

    #region Hotkey Management
    public void StartRecordingHotkey(int presetIndex)
    {
        if (cameraPresets == null || presetIndex < 0 || presetIndex >= cameraPresets.Count) return;

        var preset = cameraPresets[presetIndex];
        if (!preset.IsValid()) return;

        foreach (var otherPreset in cameraPresets)
        {
            if (otherPreset?.hotkey?.isRecording == true)
            {
                otherPreset.hotkey.StopRecording();
            }
        }

        preset.hotkey.StartRecording();
    }

    public void StopRecordingHotkey(int presetIndex)
    {
        if (cameraPresets == null || presetIndex < 0 || presetIndex >= cameraPresets.Count) return;

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