"""cg module for cleaning databases and files."""
import logging
from datetime import datetime, timedelta
from pathlib import Path
from typing import List, Optional

import click
from housekeeper.store.models import File, Version

from cg.apps.housekeeper.hk import HousekeeperAPI
from cg.apps.scout.scout_export import ScoutExportCase
from cg.apps.scout.scoutapi import ScoutAPI
from cg.cli.workflow.commands import (
    balsamic_past_run_dirs,
    balsamic_pon_past_run_dirs,
    balsamic_qc_past_run_dirs,
    balsamic_umi_past_run_dirs,
    fluffy_past_run_dirs,
    microsalt_past_run_dirs,
    mip_dna_past_run_dirs,
    mip_rna_past_run_dirs,
    mutant_past_run_dirs,
    rnafusion_past_run_dirs,
    rsync_past_run_dirs,
)
from cg.constants import EXIT_FAIL, EXIT_SUCCESS
from cg.constants.constants import DRY_RUN, SKIP_CONFIRMATION, Pipeline
from cg.constants.housekeeper_tags import ALIGNMENT_FILE_TAGS, ScoutTag
from cg.exc import CleanFlowCellFailedError
from cg.meta.clean.api import CleanAPI
from cg.meta.clean.clean_flow_cells import CleanFlowCellAPI
from cg.models.cg_config import CGConfig
from cg.store import Store
from cg.store.models import Analysis
from cg.utils.date import get_date_days_ago, get_timedelta_from_date
from cg.utils.dispatcher import Dispatcher
from cg.utils.files import get_directories_in_path

CHECK_COLOR = {True: "green", False: "red"}
LOG = logging.getLogger(__name__)
FLOW_CELL_OUTPUT_HEADERS = [
    "Flow cell run name",
    "Flow cell id",
    "Correct name?",
    "Exists in statusdb?",
    "Fastq files in HK?",
    "Spring files in HK?",
    "Files on disk?",
    "Check passed?",
]


@click.group()
def clean():
    """Clean up processes."""
    return


for sub_cmd in [
    balsamic_past_run_dirs,
    balsamic_qc_past_run_dirs,
    balsamic_umi_past_run_dirs,
    balsamic_pon_past_run_dirs,
    fluffy_past_run_dirs,
    mip_dna_past_run_dirs,
    mip_rna_past_run_dirs,
    mutant_past_run_dirs,
    rnafusion_past_run_dirs,
    rsync_past_run_dirs,
    microsalt_past_run_dirs,
]:
    clean.add_command(sub_cmd)


@clean.command("hk-alignment-files")
@click.argument("bundle")
@DRY_RUN
@SKIP_CONFIRMATION
@click.pass_obj
def hk_alignment_files(
    context: CGConfig, bundle: str, yes: bool = False, dry_run: bool = False
) -> None:
    """Clean up alignment files in Housekeeper bundle."""
    housekeeper_api: HousekeeperAPI = context.housekeeper_api
    for tag in ALIGNMENT_FILE_TAGS:
        tag_files = set(housekeeper_api.get_files(bundle=bundle, tags=[tag]))

        if not tag_files:
            LOG.warning(
                f"Could not find any files ready for cleaning for bundle {bundle} and tag {tag}"
            )

        for hk_file in tag_files:
            if not (yes or click.confirm(_get_confirm_question(bundle, hk_file))):
                continue

            file_path: Path = Path(hk_file.full_path)
            if hk_file.is_included and file_path.exists():
                LOG.info(f"Unlinking {file_path}")
                if not dry_run:
                    file_path.unlink()

            LOG.info(f"Deleting {file_path} from database")
            if not dry_run:
                housekeeper_api.delete_file(file_id=hk_file.id)
                housekeeper_api.commit()


@clean.command("scout-finished-cases")
@click.option(
    "--days-old",
    type=int,
    default=300,
    help="Clean alignment files with analysis dates older then given number of days",
)
@SKIP_CONFIRMATION
@DRY_RUN
@click.pass_context
def scout_finished_cases(
    context: click.Context, days_old: int, yes: bool = False, dry_run: bool = False
) -> None:
    """Clean up of solved and archived Scout cases."""
    scout_api: ScoutAPI = context.obj.scout_api
    bundles: List[str] = []
    for status in [ScoutTag.ARCHIVED.value, ScoutTag.SOLVED.value]:
        cases: List[ScoutExportCase] = scout_api.get_cases(status=status, reruns=False)
        cases_added: int = 0
        for case in cases:
            analysis_time_delta: timedelta = get_timedelta_from_date(date=case.analysis_date)
            if analysis_time_delta.days > days_old:
                bundles.append(case.id)
                cases_added += 1
        LOG.info(f"{cases_added} cases marked for alignment files removal")

    for bundle in bundles:
        context.invoke(hk_alignment_files, bundle=bundle, yes=yes, dry_run=dry_run)


@clean.command("hk-case-bundle-files")
@click.option(
    "--days-old",
    type=int,
    default=365,
    help="Clean all files with analysis dates older then given number of days",
)
@DRY_RUN
@click.pass_context
def hk_case_bundle_files(context: CGConfig, days_old: int, dry_run: bool = False) -> None:
    """Clean up all non-protected files for all pipelines."""
    housekeeper_api: HousekeeperAPI = context.obj.housekeeper_api
    clean_api: CleanAPI = CleanAPI(status_db=context.obj.status_db, housekeeper_api=housekeeper_api)

    size_cleaned: int = 0
    version_file: File
    for version_file in clean_api.get_unprotected_existing_bundle_files(
        before=get_date_days_ago(days_ago=days_old)
    ):
        file_path: Path = Path(version_file.full_path)
        file_size: int = file_path.stat().st_size
        size_cleaned += file_size
        if dry_run:
            LOG.info(f"Dry run: {dry_run}. Keeping file {file_path}")
            continue

        file_path.unlink()
        housekeeper_api.delete_file(file_id=version_file.id)
        housekeeper_api.commit()
        LOG.info(f"Removed file {file_path}. Dry run: {dry_run}")

    LOG.info(f"Process freed {round(size_cleaned * 0.0000000001, 2)} GB. Dry run: {dry_run}")


@clean.command("hk-bundle-files")
@click.option("-c", "--case-id", type=str, required=False)
@click.option("-p", "--pipeline", type=Pipeline, required=False)
@click.option("-t", "--tags", multiple=True, required=True)
@click.option("-o", "--days-old", type=int, default=30)
@DRY_RUN
@click.pass_obj
def hk_bundle_files(
    context: CGConfig,
    case_id: Optional[str],
    tags: list,
    days_old: Optional[int],
    pipeline: Optional[Pipeline],
    dry_run: bool,
):
    """Remove files found in Housekeeper bundles."""

    housekeeper_api: HousekeeperAPI = context.housekeeper_api
    status_db: Store = context.status_db

    date_threshold: datetime = get_date_days_ago(days_ago=days_old)

    function_dispatcher: Dispatcher = Dispatcher(
        functions=[
            status_db.get_analyses_started_at_before,
            status_db.get_analyses_for_case_and_pipeline_started_at_before,
            status_db.get_analyses_for_pipeline_started_at_before,
            status_db.get_analyses_for_case_started_at_before,
        ],
        input_dict={
            "case_internal_id": case_id,
            "pipeline": pipeline,
            "started_at_before": date_threshold,
        },
    )
    analyses: List[Analysis] = function_dispatcher()

    size_cleaned: int = 0
    for analysis in analyses:
        LOG.info(f"Cleaning analysis {analysis}")
        bundle_name: str = analysis.family.internal_id
        hk_bundle_version: Optional[Version] = housekeeper_api.version(
            bundle=bundle_name, date=analysis.started_at
        )
        if not hk_bundle_version:
            LOG.warning(
                f"Version not found for "
                f"bundle:{bundle_name}; "
                f"pipeline: {analysis.pipeline}; "
                f"date {analysis.started_at}"
            )
            continue

        LOG.info(
            f"Version found for "
            f"bundle:{bundle_name}; "
            f"pipeline: {analysis.pipeline}; "
            f"date {analysis.started_at}"
        )
        version_files: List[File] = housekeeper_api.get_files(
            bundle=analysis.family.internal_id, tags=tags, version=hk_bundle_version.id
        ).all()
        for version_file in version_files:
            file_path: Path = Path(version_file.full_path)
            if not file_path.exists():
                LOG.info(f"File {file_path} not on disk.")
                continue
            LOG.info(f"File {file_path} found on disk.")
            file_size = file_path.stat().st_size
            size_cleaned += file_size
            if dry_run:
                continue

            file_path.unlink()
            housekeeper_api.delete_file(version_file.id)
            housekeeper_api.commit()
            LOG.info(f"Removed file {file_path}. Dry run: {dry_run}")

    LOG.info(f"Process freed {round(size_cleaned * 0.0000000001, 2)}GB. Dry run: {dry_run}")


@clean.command("flow-cells")
@DRY_RUN
@click.pass_obj
def clean_flow_cells(context: CGConfig, dry_run: bool):
    """Remove flow cells from the flow cells and demultiplexed runs folder."""

    directories_to_check: List[Path] = []
    for path in [Path(context.flow_cells_dir), Path(context.demultiplexed_flow_cells_dir)]:
        directories_to_check.extend(get_directories_in_path(path))
    exit_code = EXIT_SUCCESS
    for flow_cell_directory in directories_to_check:
        try:
            clean_flow_cell_api = CleanFlowCellAPI(
                flow_cell_path=flow_cell_directory,
                status_db=context.status_db,
                housekeeper_api=context.housekeeper_api,
                dry_run=dry_run,
            )
            clean_flow_cell_api.delete_flow_cell_directory()
        except CleanFlowCellFailedError as error:
            LOG.error(repr(error))
            exit_code = EXIT_FAIL
    if exit_code:
        click.Abort


def _get_confirm_question(bundle, file_obj) -> str:
    """Return confirmation question."""
    return (
        f"{bundle}: remove file from file system and database: {file_obj.full_path}"
        if file_obj.is_included
        else f"{bundle}: remove file from database: {file_obj.full_path}"
    )
