using UnityEngine;
using System.Collections;

public class Shark : NotifierBase
{
    public AudioClip spawnSound;

    public AudioClip angrySound;

    public AudioClip tiredSound;

    public AudioClip deathSound;

    public AudioClip hitSound;

    public Player player;

    public GameObject sparkPrefab;

    public SpriteRenderer gameBoardSpriteRenderer;

    private Bounds? gameBoardBounds = null;

    private AudioSource audioSource;

    private Animator animator;

    private int angle = 0;

    private bool isAngry = false;

    private bool isTired = false;

    private bool isDead = false;

    private float angryTimer = 0f;

    private float angryDuration = 5f;

    private float angryProbability = 0.1f;

    private static int sharkKillPoints = 1000;

    private static float sparkSpawnIntervalSeconds = 1.0f / 10.0f;

    private bool spawnFinished = false;

    [SerializeField]
    private float normalMaxSpeed = 2.5f;

    [SerializeField]
    private float turnRateDegreesPerSecond = 180f;

    [SerializeField]
    private float speedChangeRate = 4f;

    [SerializeField]
    private float targetReachedDistance = 0.15f;

    [SerializeField]
    private float alignmentThresholdDegrees = 5f;

    [SerializeField]
    private float targetTimeoutSeconds = 2f;

    private SpriteRenderer sharkSpriteRenderer;

    private Bounds? movementBounds = null;

    private Vector2 currentTarget;

    private bool hasTarget = false;

    private float currentSpeed = 0f;

    private float currentHeadingAngle = 0f;

    private float currentTargetTimer = 0f;

    private float sparkSpawnTimer = 0f;

    private bool killed = false;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        audioSource = GetComponent<AudioSource>();
        animator = GetComponent<Animator>();
        sharkSpriteRenderer = GetComponent<SpriteRenderer>();

        // Ensure movement transitions stay blocked until spawn animation finishes.
        animator.SetBool("canMove", false);

        bool spawnLeft = Random.Range(0, 2) == 0;
        angle = spawnLeft ? 90 : 270;
        currentHeadingAngle = angle;

        // Initialize angle before triggering spawn, so post-spawn movement starts in the correct direction.
        animator.SetInteger("angle", angle);

        // Clear stale trigger state (useful when object is reused) before setting the selected spawn trigger.
        animator.ResetTrigger("spawnLeft");
        animator.ResetTrigger("spawnRight");
        animator.SetTrigger(spawnLeft ? "spawnLeft" : "spawnRight");
        audioSource.PlayOneShot(spawnSound);
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        // Foce reset the rotation.
        transform.rotation = Quaternion.identity;

        // Initialize gameBoardBounds.
        if (gameBoardSpriteRenderer != null && gameBoardBounds == null)
        {
            gameBoardBounds = gameBoardSpriteRenderer.bounds;
        }

        // Build movement bounds by shrinking the board by 2x the shark's size total (1x on each side).
        if (gameBoardBounds != null && sharkSpriteRenderer != null && movementBounds == null)
        {
            Bounds sharkBounds = sharkSpriteRenderer.bounds;
            Bounds boardBounds = gameBoardBounds.Value;

            float minX = boardBounds.min.x + (2.0f * sharkBounds.size.x);
            float maxX = boardBounds.max.x - (2.0f * sharkBounds.size.x);
            float minY = boardBounds.min.y + (2.0f * sharkBounds.size.y);
            float maxY = boardBounds.max.y - (2.0f * sharkBounds.size.y);

            // Fallback to board center if the shrunken region collapses.
            if (minX > maxX)
            {
                float centerX = boardBounds.center.x;
                minX = centerX;
                maxX = centerX;
            }

            if (minY > maxY)
            {
                float centerY = boardBounds.center.y;
                minY = centerY;
                maxY = centerY;
            }

            Vector3 min = new Vector3(minX, minY, boardBounds.min.z);
            Vector3 max = new Vector3(maxX, maxY, boardBounds.max.z);
            movementBounds = new Bounds((min + max) * 0.5f, max - min);
        }

        // Check if the spawn animation has finished.
        if (
            (animator.GetCurrentAnimatorStateInfo(0).IsName("SharkSpawnRight") ||
            animator.GetCurrentAnimatorStateInfo(0).IsName("SharkSpawnLeft")) &&
            animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1.0f
        )
        {
            spawnFinished = true;
            animator.SetBool("canMove", true);
        }

        if (killed)
        {
            if (!isDead)
            {
                isDead = true;

                if (animator != null)
                {
                    // Keep death state stable even if Any State transitions exist for movement.
                    animator.SetBool("canMove", false);
                    animator.Play("SharkDeath", 0, 0.0f);
                }

                audioSource.PlayOneShot(deathSound);
                player.score += sharkKillPoints + (isAngry ? 500 : 0);

                if (isAngry)
                {
                    ShowNotification("1500");
                }
                else
                {
                    ShowNotification("1000");
                }
                
                StartCoroutine(DestroyShark());
            }

            return;
        }

        // Do stuff only if the spawn animation has finished.
        if (spawnFinished)
        {
            UpdateMovement();
            animator.SetInteger("angle", angle);

            if (!isAngry && !isTired && Random.value < angryProbability)
            {
                isAngry = true;
                angryTimer = angryDuration;
                audioSource.PlayOneShot(angrySound);
            }

            if (isAngry && !isTired && angryTimer <= 0f)
            {
                isAngry = false;
                isTired = true;
                audioSource.PlayOneShot(tiredSound);
            }

            if (isAngry)
            {
                angryTimer -= Time.fixedDeltaTime;
            }
        }
    }

    private void UpdateMovement()
    {
        if (movementBounds == null)
        {
            return;
        }

        Vector2 currentPosition = transform.position;

        if (!hasTarget)
        {
            currentTarget = ChooseRandomTarget();
            hasTarget = true;
            currentTargetTimer = 0f;
        }

        currentTargetTimer += Time.fixedDeltaTime;

        Vector2 toTarget = currentTarget - currentPosition;
        if (toTarget.sqrMagnitude <= targetReachedDistance * targetReachedDistance)
        {
            currentTarget = ChooseRandomTarget();
            currentTargetTimer = 0f;
            toTarget = currentTarget - currentPosition;
        }
        else if (currentTargetTimer >= targetTimeoutSeconds)
        {
            currentTarget = ChooseRandomTarget();
            currentTargetTimer = 0f;
            toTarget = currentTarget - currentPosition;
        }

        if (toTarget.sqrMagnitude > 0.0001f)
        {
            float desiredAngle = AngleFromDirection(toTarget.normalized);
            float angleDelta = Mathf.Abs(Mathf.DeltaAngle(currentHeadingAngle, desiredAngle));

            currentHeadingAngle = Mathf.MoveTowardsAngle(
                currentHeadingAngle,
                desiredAngle,
                turnRateDegreesPerSecond * Time.fixedDeltaTime
            );

            float dynamicMaxSpeed = isAngry && !isTired ? normalMaxSpeed * 2f : normalMaxSpeed;
            float turningSpeed = normalMaxSpeed * (2f / 3f);
            float desiredSpeed = angleDelta <= alignmentThresholdDegrees ? dynamicMaxSpeed : turningSpeed;

            currentSpeed = Mathf.MoveTowards(
                currentSpeed,
                desiredSpeed,
                speedChangeRate * Time.fixedDeltaTime
            );

            Vector2 forward = DirectionFromAngle(currentHeadingAngle);
            Vector2 nextPosition = currentPosition + forward * (currentSpeed * Time.fixedDeltaTime);
            transform.position = new Vector3(nextPosition.x, nextPosition.y, transform.position.z);

            float movedDistanceSqr = (nextPosition - currentPosition).sqrMagnitude;
            if (movedDistanceSqr > 0.000001f)
            {
                sparkSpawnTimer += Time.fixedDeltaTime;
                while (sparkSpawnTimer >= sparkSpawnIntervalSeconds)
                {
                    SpawnSpark();
                    sparkSpawnTimer -= sparkSpawnIntervalSeconds;
                }
            }
            else
            {
                sparkSpawnTimer = 0f;
            }

            angle = QuantizeAngleToTen(currentHeadingAngle);
        }
    }

    private void SpawnSpark()
    {
        if (sparkPrefab == null)
        {
            return;
        }

        Instantiate(sparkPrefab, transform.position, Quaternion.identity);
    }

    private Vector2 ChooseRandomTarget()
    {
        Bounds bounds = movementBounds.Value;
        float targetX = Random.Range(bounds.min.x, bounds.max.x);
        float targetY = Random.Range(bounds.min.y, bounds.max.y);
        return new Vector2(targetX, targetY);
    }

    private static float AngleFromDirection(Vector2 direction)
    {
        float raw = Mathf.Atan2(-direction.x, -direction.y) * Mathf.Rad2Deg;
        return (raw + 360f) % 360f;
    }

    private static Vector2 DirectionFromAngle(float degrees)
    {
        float radians = degrees * Mathf.Deg2Rad;
        return new Vector2(-Mathf.Sin(radians), -Mathf.Cos(radians));
    }

    private static int QuantizeAngleToTen(float degrees)
    {
        int quantized = Mathf.RoundToInt(degrees / 10f) * 10;
        quantized = ((quantized % 360) + 360) % 360;
        return quantized;
    }

    private IEnumerator DestroyShark()
    {
        // Wait until the death state has looped twice, with a timeout fallback so this cannot hang forever.
        float elapsed = 0.0f;
        const float maxWaitSeconds = 5.0f;

        while (elapsed < maxWaitSeconds)
        {
            AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
            if (stateInfo.IsName("SharkDeath") && stateInfo.normalizedTime >= 2.0f)
            {
                break;
            }

            elapsed += Time.deltaTime;
            yield return null;
        }

        Destroy(gameObject);
    }

    void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Wall"))
        {
            Physics2D.IgnoreCollision(collision.collider, GetComponent<Collider2D>());
        }
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        if(other.gameObject.CompareTag("Bar"))
        {
            if (!audioSource.isPlaying)
            {
                audioSource.PlayOneShot(hitSound);
            }

            // Destroy all bar segments when the ball hits a bar.
            var barSegments = GameObject.FindGameObjectsWithTag("Bar");

            foreach (var barSegment in barSegments)
            {
                Destroy(barSegment);
            }

            // Notify the player that the bar has been hit.
            var player = GameObject.FindGameObjectWithTag("Player");

            if (player != null)
            {
                player.GetComponent<Player>().OnBarHit();
            }
        }
    }

    public void KillShark()
    {
        if (killed)
        {
            return;
        }

        killed = true;

        if (animator != null)
        {
            // Stop movement-related Any State transitions, then force the death animation.
            animator.SetBool("canMove", false);
            animator.ResetTrigger("killed");
            animator.Play("SharkDeath", 0, 0.0f);
        }
    }
}
