from datetime import datetime, timedelta
from pathlib import Path
from typing import List
from unittest import mock

from housekeeper.store.models import File

from cg.constants.archiving import ArchiveLocations
from cg.constants.constants import APIMethods
from cg.constants.housekeeper_tags import SequencingFileTag
from cg.io.controller import APIRequest
from cg.meta.archive.archive import (
    ARCHIVE_HANDLERS,
    FileAndSample,
    SpringArchiveAPI,
    filter_files_on_archive_location,
)
from cg.meta.archive.ddn_dataflow import AuthToken, DDNDataFlowClient, MiriaObject
from cg.meta.archive.models import ArchiveHandler, FileTransferData
from cg.models.cg_config import DataFlowConfig
from cg.store.models import Sample


def test_get_files_by_archive_location(
    spring_archive_api: SpringArchiveAPI, sample_id, father_sample_id
):
    """Tests filtering out files and samples with the correct Archive location from a list."""
    files_and_samples: List[FileAndSample] = [
        FileAndSample(
            file=spring_archive_api.housekeeper_api.get_files(bundle=sample).first(),
            sample=spring_archive_api.status_db.get_sample_by_internal_id(sample),
        )
        for sample in [sample_id, father_sample_id]
    ]

    # WHEN fetching the files by archive location
    selected_files: List[FileAndSample] = filter_files_on_archive_location(
        files_and_samples, ArchiveLocations.KAROLINSKA_BUCKET
    )

    # THEN every file returned should have that archive location
    assert selected_files
    for selected_file in selected_files:
        assert selected_file.sample.archive_location == ArchiveLocations.KAROLINSKA_BUCKET


def test_add_samples_to_files(spring_archive_api: SpringArchiveAPI):
    """Tests matching Files to Samples when both files have a matching sample."""
    # GIVEN a list of SPRING Files to archive
    files_to_archive: List[
        File
    ] = spring_archive_api.housekeeper_api.get_all_non_archived_spring_files()

    # WHEN adding the Sample objects
    file_and_samples: List[FileAndSample] = spring_archive_api.add_samples_to_files(
        files_to_archive
    )

    # THEN each file should have a matching sample
    assert len(files_to_archive) == len(file_and_samples) > 0
    for file_and_sample in file_and_samples:
        # THEN the bundle name of each file should match the sample internal id
        assert file_and_sample.file.version.bundle.name == file_and_sample.sample.internal_id


def test_add_samples_to_files_missing_sample(spring_archive_api: SpringArchiveAPI):
    """Tests matching Files to Samples when one of the files does not match a Sample."""
    # GIVEN a list of SPRING Files to archive
    files_to_archive: List[
        File
    ] = spring_archive_api.housekeeper_api.get_all_non_archived_spring_files()
    # GIVEN one of the files does not match the
    files_to_archive[0].version.bundle.name = "does-not-exist"
    # WHEN adding the Sample objects
    file_and_samples: List[FileAndSample] = spring_archive_api.add_samples_to_files(
        files_to_archive
    )

    # THEN only one of the files should have a matching sample
    assert len(files_to_archive) != len(file_and_samples) > 0
    for file_and_sample in file_and_samples:
        # THEN the bundle name of each file should match the sample internal id
        assert file_and_sample.file.version.bundle.name == file_and_sample.sample.internal_id


def test_get_sample_exists(sample_id: str, spring_archive_api: SpringArchiveAPI):
    """Tests fetching a sample when the sample exists."""
    # GIVEN a sample that exists in the database
    file: File = spring_archive_api.housekeeper_api.get_files(bundle=sample_id).first()

    # WHEN getting the sample
    sample: Sample = spring_archive_api.get_sample(file)

    # THEN the correct sample should be returned
    assert sample.internal_id == sample_id


def test_get_sample_not_exists(
    caplog,
    spring_archive_api: SpringArchiveAPI,
    sample_id,
):
    """Tests fetching a sample when the sample does not exist."""
    # GIVEN a sample that does not exist in the database
    file: File = spring_archive_api.housekeeper_api.get_files(bundle=sample_id).first()
    sample_id: str = "non-existent-sample"
    file.version.bundle.name = sample_id

    # WHEN getting the sample
    sample: Sample = spring_archive_api.get_sample(file)

    # THEN the no sample should be returned
    # THEN both sample_id and file path should be logged
    assert not sample
    assert sample_id in caplog.text
    assert file.path in caplog.text


def test_convert_into_transfer_data(
    sample_id: str, spring_archive_api: SpringArchiveAPI, ddn_dataflow_config: DataFlowConfig
):
    """Tests instantiating the correct dataclass for a sample."""
    # GIVEN file and Sample
    file_and_sample = FileAndSample(
        file=spring_archive_api.housekeeper_api.get_files(bundle=sample_id).first(),
        sample=spring_archive_api.status_db.get_sample_by_internal_id(sample_id),
    )
    with mock.patch.object(
        DDNDataFlowClient,
        "_set_auth_tokens",
        return_value=123,
    ):
        # WHEN calling the corresponding archive method
        data_flow_client: ArchiveHandler = ARCHIVE_HANDLERS[ArchiveLocations.KAROLINSKA_BUCKET](
            config=ddn_dataflow_config
        )
    # WHEN using it to instantiate the correct class
    transferdata: List[FileTransferData] = data_flow_client.convert_into_transfer_data(
        files_and_samples=[file_and_sample],
    )

    # THEN the returned object should be of the correct type
    assert isinstance(transferdata[0], MiriaObject)


def test_call_corresponding_archiving_method(spring_archive_api: SpringArchiveAPI, sample_id: str):
    """Tests so that the correct archiving function is used when providing a Karolinska customer."""
    # GIVEN a file to be transferred
    # GIVEN a spring_archive_api with a mocked archive function
    file_and_sample = FileAndSample(
        file=spring_archive_api.housekeeper_api.get_files(bundle=sample_id).first(),
        sample=spring_archive_api.status_db.get_sample_by_internal_id(sample_id),
    )

    with mock.patch.object(
        DDNDataFlowClient,
        "_set_auth_tokens",
        return_value=123,
    ), mock.patch.object(
        DDNDataFlowClient,
        "archive_files",
        return_value=123,
    ) as mock_request_submitter:
        # WHEN calling the corresponding archive method
        spring_archive_api.archive_files(
            files=[file_and_sample], archive_location=ArchiveLocations.KAROLINSKA_BUCKET
        )

    # THEN the correct archive function should have been called once
    mock_request_submitter.assert_called_once_with(files_and_samples=[file_and_sample])


def test_archive_all_non_archived_spring_files(
    spring_archive_api: SpringArchiveAPI,
    caplog,
    ok_ddn_response,
    archive_request_json,
    header_with_test_auth_token,
):
    """Test archiving all non-archived SPRING files for Miria customers."""
    # GIVEN a populated status_db database with two customers, one DDN and one non-DDN,
    # with the DDN customer having two samples, and the non-DDN having one sample.

    # WHEN archiving all available files
    with mock.patch.object(
        AuthToken,
        "model_validate",
        return_value=AuthToken(
            access="test_auth_token",
            expire=int((datetime.now() + timedelta(minutes=20)).timestamp()),
            refresh="test_refresh_token",
        ),
    ), mock.patch.object(
        APIRequest,
        "api_request_from_content",
        return_value=ok_ddn_response,
    ) as mock_request_submitter:
        spring_archive_api.archive_all_non_archived_spring_files()

    # THEN the DDN archiving function should have been called with the correct destination and source.
    mock_request_submitter.assert_called_with(
        api_method=APIMethods.POST,
        url="some/api/files/archive",
        headers=header_with_test_auth_token,
        json=archive_request_json,
    )

    # THEN all spring files for Karolinska should have an entry in the Archive table in HouseKeeper while no other
    # files should have an entry
    files: List[File] = spring_archive_api.housekeeper_api.files()
    for file in files:
        if SequencingFileTag.SPRING in [tag.name for tag in file.tags]:
            sample: Sample = spring_archive_api.status_db.get_sample_by_internal_id(
                file.version.bundle.name
            )
            if sample and sample.archive_location == ArchiveLocations.KAROLINSKA_BUCKET:
                assert file.archive
            else:
                assert not file.archive
        else:
            assert not file.archive


def test_retrieve_samples(
    spring_archive_api: SpringArchiveAPI,
    caplog,
    ok_ddn_response,
    trimmed_local_path,
    local_storage_repository,
    retrieve_request_json,
    header_with_test_auth_token,
    sample_with_spring_file: str,
):
    """Test retrieving all archived SPRING files tied to a sample for a Miria customer."""
    # GIVEN a populated status_db database with two customers, one DDN and one non-DDN,
    # with the DDN customer having two samples, and the non-DDN having one sample.

    files: List[File] = spring_archive_api.housekeeper_api.get_files(
        bundle=sample_with_spring_file, tags=[SequencingFileTag.SPRING]
    )
    for file in files:
        spring_archive_api.housekeeper_api.add_archives(
            files=[Path(file.full_path)], archive_task_id=123
        )
        assert not file.archive.retrieval_task_id
        assert file.archive

    # WHEN archiving all available files
    with mock.patch.object(
        AuthToken,
        "model_validate",
        return_value=AuthToken(
            access="test_auth_token",
            expire=int((datetime.now() + timedelta(minutes=20)).timestamp()),
            refresh="test_refresh_token",
        ),
    ), mock.patch.object(MiriaObject, "trim_path", return_value=True), mock.patch.object(
        APIRequest,
        "api_request_from_content",
        return_value=ok_ddn_response,
    ) as mock_request_submitter:
        spring_archive_api.retrieve_samples([sample_with_spring_file])

    retrieve_sample_request_json = retrieve_request_json.copy()
    retrieve_sample_request_json["pathInfo"][0]["destination"] = (
        local_storage_repository + files[0].version.full_path.as_posix()
    )

    # THEN the DDN archiving function should have been called with the correct destination and source.
    mock_request_submitter.assert_called_with(
        api_method=APIMethods.POST,
        url="some/api/files/retrieve",
        headers=header_with_test_auth_token,
        json=retrieve_sample_request_json,
    )

    # THEN the Archive entry should have a retrieval task id set
    for file in files:
        assert file.archive.retrieval_task_id
