import pytest
import random
import uuid

from pathlib import Path

from Bio import SeqIO
from Bio.Seq import Seq
from Bio.SeqRecord import SeqRecord
from click.testing import CliRunner

from primalscheme import config


def seq_record_factory(seq_len=5000, alphabet="acgt", id=""):
    """Generate a random SeqRecord for testing purposes"""
    id = id or f"random_seq_{uuid.uuid4()}"
    seq = "".join([random.choice(alphabet) for i in range(seq_len)])
    return SeqRecord(Seq(seq), id=id)


def multi_seq_generator(n, **kwargs):
    """Generate multiple random SeqRecords for testing purposes"""
    return [seq_record_factory(**kwargs) for i in range(n)]


@pytest.fixture(scope="session")
def stored_inputs_path():
    """Return path to stored test inputs dir"""
    return Path(__file__).parent / "inputs"


@pytest.fixture(scope="session")
def temp_inputs_path(tmp_path_factory):
    """Return a temp_path for generating input files"""
    return tmp_path_factory.mktemp("inputs")


@pytest.fixture(scope="session")
def input_fasta_empty(temp_inputs_path):
    """Generate an empty fasta input file"""
    fh = temp_inputs_path / "empty_input.fa"
    fh.write_text("")
    return fh


@pytest.fixture(scope="session")
def input_fasta_invalid_alphabet(temp_inputs_path):
    """Generate a fasta file with one valid record and one with an invalid alphabet"""
    fh = temp_inputs_path / "invalid_alphabet.fa"
    records = [seq_record_factory(), seq_record_factory(alphabet="acgtXZ")]
    SeqIO.write(records, fh, "fasta")
    return fh


@pytest.fixture(scope="session")
def input_fasta_5_random_valid(temp_inputs_path):
    """Generate a random multi-FASTA with 5 valid records"""
    fh = temp_inputs_path / "valid_random_5_fasta.fa"
    SeqIO.write(multi_seq_generator(5), fh, "fasta")
    return fh


@pytest.fixture(scope="session")
def input_fasta_too_many_refs_random_valid(temp_inputs_path):
    """Generate a random multi-FASTA with too many records"""
    fh = temp_inputs_path / "valid_random_too_many_refs_fasta.fa"
    SeqIO.write(multi_seq_generator(config.MAX_REFERENCES + 1), fh, "fasta")
    return fh


@pytest.fixture(scope="session")
def input_fasta_valid_with_gaps(temp_inputs_path):
    """Generate a FASTA that includes gaps - """
    fh = temp_inputs_path / "valid_fasta_with_gaps.fa"
    SeqIO.write(seq_record_factory(alphabet="acgt-"), fh, "fasta")
    return fh


@pytest.fixture(scope="session")
def input_fasta_size_difference_over_500(temp_inputs_path):
    """Generate a FASTA where the  record is over 500nt longer than the first"""
    fh = temp_inputs_path / "invalid_different.fa"
    records = [seq_record_factory(seq_len=5000), seq_record_factory(seq_len=5501)]
    SeqIO.write(records, fh, "fasta")
    return fh


@pytest.fixture(scope="session")
def input_fasta_short_500(temp_inputs_path):
    """Generate a FASTA where the record is short"""
    fh = temp_inputs_path / "invalid_short.fa"
    SeqIO.write(seq_record_factory(seq_len=500), fh, "fasta")
    return fh


STORED_INPUTS = [
    "CHIKV_demo.fa",
    "Ebov-10-Pan.fasta",
    "nCov-2019.fasta",
    "embB_high_gc.fasta",
    "CHIKV_ambig.fa",
]


@pytest.fixture(params=STORED_INPUTS)
def all_stored_inputs(request, stored_inputs_path):
    """All stored input paths."""
    return stored_inputs_path / request.param


@pytest.fixture(scope="session")
def chikv_input(stored_inputs_path):
    """Path to chikv input."""
    return stored_inputs_path / STORED_INPUTS[0]


@pytest.fixture(scope="session")
def ebola_input(stored_inputs_path):
    """Path to ebola input."""
    return stored_inputs_path / STORED_INPUTS[1]


@pytest.fixture(scope="session")
def ncov_single_ref_input(stored_inputs_path):
    """Path to ncov input."""
    return stored_inputs_path / STORED_INPUTS[2]


@pytest.fixture(scope="session")
def high_gc_input(stored_inputs_path):
    """Path to high-gc input."""
    return stored_inputs_path / STORED_INPUTS[3]


@pytest.fixture(scope="session")
def chikv_ambig_input(stored_inputs_path):
    """Path to chikv input (with ambiguity codes)."""
    return stored_inputs_path / STORED_INPUTS[4]


@pytest.fixture(scope="session")
def random_reference_slice():
    """Generate a random reference slice."""
    record = seq_record_factory(seq_len=500)
    return record


@pytest.fixture(scope="session")
def cli_runner():
    """CliRunner instance."""
    return CliRunner()
