"""Tests for amino acid property tables and sequence analysis functions."""
import numpy as np
import pytest

from peptidegym.peptide.properties import (
    AMINO_ACIDS,
    AA_TO_INDEX,
    INDEX_TO_AA,
    HYDROPHOBICITY,
    MOLECULAR_WEIGHT,
    CHARGE_PH74,
    net_charge,
    mean_hydrophobicity,
    aromaticity_fraction,
    molecular_weight,
    one_hot_encode,
)


def test_amino_acid_count():
    """There are exactly 20 standard amino acids."""
    assert len(AMINO_ACIDS) == 20


def test_aa_to_index_mapping():
    """AA_TO_INDEX maps every amino acid to a unique index 0-19."""
    assert len(AA_TO_INDEX) == 20
    indices = sorted(AA_TO_INDEX.values())
    assert indices == list(range(20))
    for aa in AMINO_ACIDS:
        assert aa in AA_TO_INDEX


def test_index_to_aa_mapping():
    """INDEX_TO_AA is the inverse of AA_TO_INDEX."""
    assert len(INDEX_TO_AA) == 20
    for aa, idx in AA_TO_INDEX.items():
        assert INDEX_TO_AA[idx] == aa


def test_hydrophobicity_known_values():
    """Kyte-Doolittle scale: I=4.5 (most hydrophobic), R=-4.5 (most hydrophilic)."""
    assert HYDROPHOBICITY["I"] == pytest.approx(4.5)
    assert HYDROPHOBICITY["R"] == pytest.approx(-4.5)


def test_molecular_weight_known_values():
    """Glycine is the lightest (75.03), tryptophan the heaviest (204.23)."""
    assert MOLECULAR_WEIGHT["G"] == pytest.approx(75.03)
    assert MOLECULAR_WEIGHT["W"] == pytest.approx(204.23)


def test_charge_positive():
    """K and R carry +1 charge at pH 7.4."""
    assert CHARGE_PH74["K"] == pytest.approx(1.0)
    assert CHARGE_PH74["R"] == pytest.approx(1.0)


def test_charge_negative():
    """D and E carry -1 charge at pH 7.4."""
    assert CHARGE_PH74["D"] == pytest.approx(-1.0)
    assert CHARGE_PH74["E"] == pytest.approx(-1.0)


def test_net_charge_cationic():
    """KRKR should have net charge +4."""
    assert net_charge("KRKR") == pytest.approx(4.0)


def test_net_charge_anionic():
    """DEDE should have net charge -4."""
    assert net_charge("DEDE") == pytest.approx(-4.0)


def test_mean_hydrophobicity_hydrophobic():
    """ILVA are all hydrophobic, mean should be positive."""
    result = mean_hydrophobicity("ILVA")
    assert result > 0.0


def test_mean_hydrophobicity_hydrophilic():
    """RKDE are all hydrophilic/charged, mean should be negative."""
    result = mean_hydrophobicity("RKDE")
    assert result < 0.0


def test_aromaticity_fraction_aromatic():
    """FWYH are all aromatic, fraction should be 1.0."""
    assert aromaticity_fraction("FWYH") == pytest.approx(1.0)


def test_aromaticity_fraction_nonaromatic():
    """AGLV contain no aromatics, fraction should be 0.0."""
    assert aromaticity_fraction("AGLV") == pytest.approx(0.0)


def test_molecular_weight_dipeptide():
    """Dipeptide MW = sum of residue MWs minus one water (18.02)."""
    expected = MOLECULAR_WEIGHT["A"] + MOLECULAR_WEIGHT["G"] - 18.02
    assert molecular_weight("AG") == pytest.approx(expected, rel=1e-4)


def test_one_hot_encode_shape():
    """one_hot_encode returns (max_length, 20) array."""
    arr = one_hot_encode("ACGT", max_length=10)
    assert arr.shape == (10, 20)
    assert arr.dtype == np.float32
    # First 3 valid AAs should have exactly one hot position each
    for i, aa in enumerate("ACG"):
        row = arr[i]
        assert row.sum() == pytest.approx(1.0)
        assert row[AA_TO_INDEX[aa]] == pytest.approx(1.0)
    # 'T' is a valid AA (threonine)
    assert arr[3, AA_TO_INDEX["T"]] == pytest.approx(1.0)
