import click
import os
import json
import csv
try:
    import tomllib
except ModuleNotFoundError:
    import pip._vendor.tomli as tomllib
from pathlib import Path
from importlib import metadata as importlib_metadata
from .client import AbromicsClient
from .utils import FILE_TYPES, SEQUENCING_TYPES


def get_version() -> str:
    """Best-effort version resolution for the CLI.

    Priority:
    1. Installed package metadata (normal pip install)
    2. Local pyproject.toml (editable/development install)
    3. Fallback to 'unknown'
    """
    try:
        return importlib_metadata.version("abromics-library")
    except importlib_metadata.PackageNotFoundError:
        pass
    except Exception:
        pass

    try:
        pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
        with open(pyproject_path, "rb") as f:
            data = tomllib.load(f)
            return data["project"]["version"]
    except (FileNotFoundError, KeyError, tomllib.TOMLDecodeError):
        return "unknown"

__version__ = get_version()


def require_client(ctx):
    """Helper function to ensure client is available for API-dependent commands."""
    if ctx.obj['client'] is None:
        click.echo("❌ This command requires an API key. Please provide --api-key or set ABROMICS_API_KEY environment variable.", err=True)
        ctx.exit(1)
    return ctx.obj['client']


@click.group()
@click.option('--api-key', envvar='ABROMICS_API_KEY', help='ABRomics API key')
@click.option('--base-url', envvar='ABROMICS_BASE_URL', default='https://analysis.abromics.fr', help='ABRomics API base URL')
@click.version_option(version=__version__, prog_name='abromics')
@click.pass_context
def cli(ctx, api_key, base_url):
    ctx.ensure_object(dict)
    if api_key:
        ctx.obj['client'] = AbromicsClient(api_key=api_key, base_url=base_url)
    else:
        ctx.obj['client'] = None


@cli.group('project')
@click.pass_context
def project_group(ctx):
    """Manage ABRomics projects."""
    pass


@project_group.command('create')
@click.option('--name', required=True, help='Project name')
@click.option('--template', required=True, type=int, help='Template ID to use')
@click.option('--description', required=False, help='Optional project description')
@click.pass_context
def create_project(ctx, name, template, description):
    """Create a new project."""
    client = require_client(ctx)
    try:
        project = client.projects.create(name=name, template=template, description=description)
        click.echo(f"✅ Created project: {project.name} (ID: {project.id})")
    except Exception as e:
        click.echo(f"❌ Failed to create project: {str(e)}", err=True)







def _format_check_data_table(rows):
    """Format list of (strain_id, sample_name, metadata_status, assembly_status) as ASCII table."""
    if not rows:
        return None
    col_strain = max(10, max(len(r[0]) for r in rows))
    col_sample = max(12, max(len(r[1]) for r in rows))
    col_meta = 8
    col_asm = 8
    sep = "  "
    header = (
        "Strain ID".ljust(col_strain) + sep
        + "Sample name".ljust(col_sample) + sep
        + "Metadata".ljust(col_meta) + sep
        + "Assembly"
    )
    line = "-" * len(header)
    lines = [header, line]
    for strain_id, sample_name, meta_status, asm_status in rows:
        lines.append(
            strain_id.ljust(col_strain) + sep
            + sample_name.ljust(col_sample) + sep
            + meta_status.ljust(col_meta) + sep
            + asm_status
        )
    return "\n".join(lines)


def _upload_status_label(r):
    """Return short status label for an upload result."""
    if r.get('success'):
        return 'OK'
    err = (r.get('error') or '').lower()
    if 'already uploaded' in err or 'already exists' in err:
        return 'skipped'
    msg = (r.get('error') or 'failed')[:40]
    if len((r.get('error') or '')) > 40:
        msg += '…'
    return 'fail: ' + msg


def _format_workflow_summary_table(samples_data, project_lookup, all_upload_results, file_type):
    """Build a summary table: one row per sample with Sample name, Strain name, Project, filenames, status, Sample type, Host species, Microorganism, Country."""
    # Key (project_name, sample_name, filename_basename) -> status label
    status_lookup = {}
    for r in all_upload_results:
        proj = r.get('project_name', '')
        sname = (r.get('sample_name') or '').strip()
        path = r.get('file_path')
        fn = os.path.basename(path) if path else ''
        if not fn:
            continue
        status_lookup[(proj, sname, fn)] = _upload_status_label(r)

    # Only samples that belong to projects we processed
    rows = []
    for sd in samples_data:
        proj = sd.get('project', '')
        if proj not in project_lookup:
            continue
        sample_name = (sd.get('original_sample_id') or sd.get('sample_name') or '-').strip()
        strain_name = (sd.get('strain_id') or '-').strip()
        sample_type = (sd.get('type') or '-').strip()
        host = (sd.get('species') or '-').strip()
        micro = (sd.get('scientific_name') or '-').strip()
        country = (sd.get('country_id') or '-').strip()
        if isinstance(country, int):
            country = str(country)

        def _is_fail(s):
            return s and str(s).strip().lower().startswith('fail:')

        if file_type == 'fastq':
            r1 = (sd.get('r1_fastq_filename') or '-').strip()
            r2 = (sd.get('r2_fastq_filename') or '-').strip()
            if r1 != '-':
                r1 = os.path.basename(r1)
            if r2 != '-':
                r2 = os.path.basename(r2)
            st1 = status_lookup.get((proj, sample_name, r1), '-') if r1 != '-' else '-'
            st2 = status_lookup.get((proj, sample_name, r2), '-') if r2 != '-' else '-'
            row_ok = not _is_fail(st1) and not _is_fail(st2)
            rows.append({
                'status': '✅' if row_ok else '❌',
                'sample_name': sample_name,
                'strain_name': strain_name,
                'project': proj,
                'file1': r1,
                'status1': st1,
                'file2': r2,
                'status2': st2,
                'sample_type': sample_type,
                'host': host,
                'microorganism': micro,
                'country': country,
            })
        else:
            fa = (sd.get('fasta_filename') or '-').strip()
            if fa != '-':
                fa = os.path.basename(fa)
            st = status_lookup.get((proj, sample_name, fa), '-') if fa != '-' else '-'
            row_ok = not _is_fail(st)
            rows.append({
                'status': '✅' if row_ok else '❌',
                'sample_name': sample_name,
                'strain_name': strain_name,
                'project': proj,
                'file1': fa,
                'status1': st,
                'file2': '',
                'status2': '',
                'sample_type': sample_type,
                'host': host,
                'microorganism': micro,
                'country': country,
            })

    if not rows:
        return None

    def _str(x):
        return str(x) if x is not None else '-'

    sep = '  '
    if file_type == 'fastq':
        headers = ['Status', 'Sample name', 'Strain name', 'Project', 'R1 file', 'R1 status', 'R2 file', 'R2 status', 'Sample type', 'Host species', 'Microorganism', 'Country']
        col_keys = ['status', 'sample_name', 'strain_name', 'project', 'file1', 'status1', 'file2', 'status2', 'sample_type', 'host', 'microorganism', 'country']
    else:
        headers = ['Status', 'Sample name', 'Strain name', 'Project', 'Fasta file', 'Upload', 'Sample type', 'Host species', 'Microorganism', 'Country']
        col_keys = ['status', 'sample_name', 'strain_name', 'project', 'file1', 'status1', 'sample_type', 'host', 'microorganism', 'country']

    widths = []
    for i, k in enumerate(col_keys):
        w = max(len(headers[i]), 4)
        for r in rows:
            w = max(w, min(len(_str(r.get(k))), 48))
        widths.append(min(w, 48))

    header_line = sep.join(headers[i][:widths[i]].ljust(widths[i]) for i in range(len(col_keys)))
    div = '-' * len(header_line)
    out = [header_line, div]
    for r in rows:
        out.append(sep.join(_str(r.get(k, ''))[:widths[i]].ljust(widths[i]) for i, k in enumerate(col_keys)))
    return '\n'.join(out)


@cli.command('check-data')
@click.option('--strain-name', required=True, help='Strain name (Strain ID) to look up; can match multiple samples in the project')
@click.option('--project-id', required=True, type=int, help='Project ID to scope the check')
@click.pass_context
def check_data(ctx, strain_name, project_id):
    """List strain/sample combinations matching the strain name in the project, with metadata and assembly status (table)."""
    client = require_client(ctx)
    try:
        experiments = []
        page = 1
        page_size = 100
        while True:
            response = client.get(
                '/api/experiment/informations/',
                params={
                    'project_id': project_id,
                    'strain_id': strain_name,
                    'ordering': '-create_time',
                    'page_size': page_size,
                    'page': page,
                },
            )
            payload = response.json()
            if 'results' in payload:
                page_experiments = payload['results']
                if not page_experiments:
                    break
                experiments.extend(page_experiments)
                if not payload.get('next'):
                    break
                page += 1
            else:
                page_experiments = payload if isinstance(payload, list) else [payload]
                experiments.extend(page_experiments)
                break
        # Unique (strain_id, original_sample_id) -> (metadata present, assembly present)
        seen = {}
        for exp in experiments:
            for sample in exp.get('samples', []):
                strain_id = (sample.get('strain_id') or '').strip() or '-'
                sample_name = (sample.get('original_sample_id') or '').strip() or '-'
                key = (strain_id, sample_name)
                has_assembly = any(
                    (f.get('file_type') or '').lower() == 'fasta'
                    for f in sample.get('rawinputfiles', [])
                )
                if key not in seen:
                    seen[key] = {'metadata': True, 'assembly': has_assembly}
                else:
                    seen[key]['assembly'] = seen[key]['assembly'] or has_assembly
        rows = [
            (strain_id, sample_name, 'present', 'present' if info['assembly'] else 'absent')
            for (strain_id, sample_name), info in sorted(seen.items())
        ]
        table = _format_check_data_table(rows)
        if table:
            click.echo(table)
        else:
            click.echo('absent')
    except Exception as e:
        click.echo(f"❌ Error: {e}", err=True)
        ctx.exit(1)


@cli.command('complete-upload-workflow')
@click.option('--metadata-tsv', required=True, help='Path to TSV metadata file')
@click.option('--data-dir', required=True, help='Directory containing data files to upload')
@click.option(
    '--summary-format',
    type=click.Choice(['table', 'tsv', 'json']),
    default='table',
    show_default=True,
    help='Format for final workflow summary (table = human-readable, tsv/json = machine-readable).',
)
@click.option(
    '--summary-output',
    type=click.Path(dir_okay=False, writable=True, resolve_path=True),
    default='-',
    show_default=True,
    help='File to write summary to ("-" = standard output).',
)
@click.pass_context
def complete_upload_workflow(ctx, metadata_tsv, data_dir, summary_format, summary_output):
    """Complete workflow: create samples from TSV and upload files automatically."""
    import os
    
    client = require_client(ctx)
    
    tsv_path = metadata_tsv
    
    if not os.path.exists(tsv_path):
        click.echo(f"❌ TSV file not found: {tsv_path}", err=True)
        return
    
    try:
        click.echo("🚀 Starting complete workflow...")
        click.echo(f"   TSV file: {tsv_path}")
        click.echo(f"   Data directory: {data_dir}")
        
        # Step 1: Parse TSV file and detect file type
        click.echo("\n📊 Step 1: Processing TSV file...")
        samples_data = client.batch.tsv_processor._parse_tsv(tsv_path)
        total_samples_expected = len(samples_data)
        
        if not samples_data:
            click.echo("❌ No data found in TSV file", err=True)
            return
        
        file_type = "fastq"  # default
        file_pattern = "*.fastq.gz"  # default
        sample_columns = samples_data[0].keys() if samples_data else []
        
        if any('r1' in col.lower() or 'r2' in col.lower() for col in sample_columns):
            file_type = "fastq"
            file_pattern = "*.fastq.gz"
            click.echo(f"   📁 Detected file type: FASTQ (found R1/R2 columns)")
        elif any('fasta' in col.lower() for col in sample_columns):
            file_type = "fasta"
            file_pattern = "*.fasta"
            click.echo(f"   📁 Detected file type: FASTA")
        else:
            click.echo(f"   📁 Using default file type: FASTQ")
        
        click.echo(f"   📁 File pattern: {file_pattern}")

        if not any('project' in sample for sample in samples_data):
            click.echo("❌ TSV must contain the required column 'Project Name*'.", err=True)
            return
        
        projects_data = {}
        duplicate_sample_names_by_project = {}
        for sample_data in samples_data:
            project_name = sample_data.get('project', 'Default Project')
            if project_name not in projects_data:
                projects_data[project_name] = []
                duplicate_sample_names_by_project[project_name] = set()
            projects_data[project_name].append(sample_data)
        
        click.echo(f"Found {len(projects_data)} projects: {', '.join(projects_data.keys())}")
        
        # Step 2: Find existing projects
        click.echo("\n📁 Step 2: Finding existing projects...")
        project_lookup = {}
        missing_projects = set()
        
        all_projects = []
        page = 1
        page_size = 100
        
        try:
            while True:
                response = client.get('/api/project/', params={
                    'detailed_project': 'true',
                    'ordering': '-create_time',
                    'page': page,
                    'page_size': page_size
                })
                data = response.json()
                
                if 'results' in data:
                    api_projects_data = data['results']
                else:
                    api_projects_data = data if isinstance(data, list) else [data]
                
                if not api_projects_data:
                    break
                
                from abromics.models.project import Project
                projects = [Project.from_dict(project) for project in api_projects_data]
                all_projects.extend(projects)
                
                if len(projects) < page_size:
                    break
                page += 1
        except Exception as e:
            click.echo(f"❌ Error fetching projects: {str(e)}", err=True)
            return
        
        for project_name in projects_data.keys():
            click.echo(f"Looking for project: {project_name}")
            
            matching_projects = [p for p in all_projects if p.name.lower() == project_name.lower()]
            
            if len(matching_projects) == 0:
                click.echo(f"  ❌ Project '{project_name}' not found. Please create this project first.", err=True)
                missing_projects.add(project_name)
                continue
            elif len(matching_projects) > 1:
                click.echo(f"  ❌ Multiple projects found with name '{project_name}':", err=True)
                for i, project in enumerate(matching_projects, 1):
                    click.echo(f"    {i}. ID: {project.id}, Name: '{project.name}'", err=True)
                click.echo(f"  Please rename one of these projects to avoid conflicts.", err=True)
                continue
            else:
                project = matching_projects[0]
                project_lookup[project_name] = project.id
                click.echo(f"  ✅ Found project: {project.name} (ID: {project.id})")
        
        click.echo("\n🧬 Step 3: Creating experiments with samples...")
        sample_results = []
        total_samples = 0
        for project_name, samples in projects_data.items():
            if project_name not in project_lookup:
                continue
                
            project_id = project_lookup[project_name]
            click.echo(f"Creating {len(samples)} experiment(s) with sample(s) for project: {project_name}")
            
            for i, sample_data in enumerate(samples):
                sample_id = sample_data.get('original_sample_id', sample_data.get('sample_name', f'sample_{i+1}'))
                click.echo(f"  Creating experiment {i+1}/{len(samples)}: {sample_id}")
                
                try:
                    sample = client.samples.create(
                        project_id=project_id,
                        metadata=sample_data
                    )
                    click.echo(f"    ✅ Created experiment with sample {sample_id} (Sample ID: {sample.sample_id}, Experiment ID: {sample.id})")
                    total_samples += 1
                    sample_results.append({
                        'project': project_name,
                        'sample_name': sample_id,
                        'strain_name': sample_data.get('strain_id', ''),
                        'experiment_id': sample.id,
                        'sample_id': sample.sample_id,
                        'success': True,
                        'error': None,
                        'duplicate_existing': False,
                    })
                except Exception as e:
                    if 'Pair association of Sample ID and Strain ID already exists' in str(e):
                        click.echo(f"    ⚠️  Sample {sample_id} already exists, will upload files to existing sample", err=False)
                        duplicate_sample_names_by_project[project_name].add(sample_id)
                        sample_results.append({
                            'project': project_name,
                            'sample_name': sample_id,
                            'strain_name': sample_data.get('strain_id', ''),
                            'experiment_id': None,
                            'sample_id': None,
                            'success': True,
                            'error': str(e),
                            'duplicate_existing': True,
                        })
                    else:
                        click.echo(f"    ❌ Failed to create experiment {sample_id}: {str(e)}", err=True)
                        sample_results.append({
                            'project': project_name,
                            'sample_name': sample_id,
                            'strain_name': sample_data.get('strain_id', ''),
                            'experiment_id': None,
                            'sample_id': None,
                            'success': False,
                            'error': str(e),
                            'duplicate_existing': False,
                        })
        
        click.echo(f"\n✅ Successfully created {total_samples} experiment(s) with samples across {len(project_lookup)} project(s)")
        
        filename_context = {}
        try:
            for sd in samples_data:
                proj = sd.get('project')
                sname = sd.get('sample_name')
                for key in ('r1_fastq_filename', 'r2_fastq_filename', 'fasta_filename'):
                    fn = sd.get(key)
                    if fn:
                        filename_context[os.path.basename(fn)] = {
                            'project': proj,
                            'sample_name': sname
                        }
        except Exception:
            filename_context = {}

        # Step 4: Upload files
        click.echo("\n📁 Step 4: Uploading files...")
        total_uploaded = 0
        total_skipped = 0
        total_files_attempted = 0
        all_upload_results = []
        for project_name, project_id in project_lookup.items():
            click.echo(f"Uploading files for project: {project_name} (ID: {project_id})")
            
            try:
                def progress_callback(current, total, filename):
                    click.echo(f"  Uploading {current}/{total}: {filename}")
                
                results = client.batch.upload_files_for_project(
                    project_id=project_id,
                    files_directory=data_dir,
                    file_pattern=file_pattern,
                    progress_callback=progress_callback,
                    tsv_data=samples_data,
                    exclude_sample_names=[]
                )
                for r in results:
                    rec = dict(r)
                    rec['project_name'] = project_name
                    all_upload_results.append(rec)
                
                # Show results
                total_files_attempted += len(results)
                successful = [r for r in results if r['success']]
                failed = [r for r in results if not r['success']]
                skipped = [r for r in results if not r['success'] and 'already uploaded' in r.get('error', '').lower()]
                actual_failures = [r for r in results if not r['success'] and 'already uploaded' not in r.get('error', '').lower()]
                
                click.echo(f"  ✅ Successfully uploaded {len(successful)} files")
                if skipped:
                    click.echo(f"  ⏭️  Skipped {len(skipped)} files (already uploaded)")
                    total_skipped += len(skipped)
                if actual_failures:
                    click.echo(f"  ❌ Failed to upload {len(actual_failures)} files:")
                    for fr in actual_failures[:20]:
                        path = fr.get('file_path')
                        err_msg = fr.get('error')
                        sample_name = fr.get('sample_name')
                        derived_project = None
                        if path:
                            filename = os.path.basename(path)
                            ctx = filename_context.get(filename)
                            if ctx:
                                derived_project = ctx.get('project')
                                if not sample_name:
                                    sample_name = ctx.get('sample_name')
                        context_parts = []
                        if derived_project:
                            context_parts.append(f"project: {derived_project}")
                        if sample_name:
                            context_parts.append(f"sample: {sample_name}")
                        context = f" [" + ", ".join(context_parts) + "]" if context_parts else ""
                        click.echo(f"    - {path or 'unknown'}: {err_msg}{context}")
                    if len(actual_failures) > 20:
                        click.echo(f"    ... and {len(actual_failures) - 20} more")
                
                total_uploaded += len(successful)
                
            except Exception as e:
                click.echo(f"  ❌ Error uploading files for project {project_name}: {e}", err=True)
        
        click.echo("\n######## Recap ########")
        click.echo(f"\n🎉 Complete workflow finished!")
        click.echo(f"   Projects found: {len(project_lookup)}")
        click.echo(f"   Experiments created: {total_samples}/{total_samples_expected}")
        click.echo(f"   Files uploaded successfully: {total_uploaded}/{total_files_attempted}")
        if total_skipped > 0:
            click.echo(f"   Files skipped (already uploaded): {total_skipped}")
        click.echo(f"   File type detected: {file_type.upper()}")

        summary_table = _format_workflow_summary_table(
            samples_data, project_lookup, all_upload_results, file_type
        )
        if summary_table and summary_format == 'table':
            click.echo("\n📋 Summary by sample:")
            click.echo(summary_table)

        if missing_projects:
            click.echo("\n⚠️  Missing projects detected (files for these projects were skipped):", err=True)
            for p in sorted(missing_projects):
                click.echo(f"   - {p}", err=True)
            click.echo("\n👉 Create the missing projects, for example:")
            for p in sorted(missing_projects):
                click.echo(f"   abromics --api-key <API_KEY> --base-url <BASE_URL> project create --name '{p}' --template <TEMPLATE_ID>")

        # Prepare a sanitized copy of upload results for export (no internal TUS URLs)
        uploads_for_export = []
        for rec in all_upload_results:
            clean = dict(rec)
            # Do not expose internal storage URLs or low-level IDs in summaries
            clean.pop('tus_url', None)
            clean.pop('raw_input_file_id', None)
            uploads_for_export.append(clean)

        workflow_result = {
            'success': True,
            'file_type': file_type,
            'projects': {name: proj_id for name, proj_id in project_lookup.items()},
            'samples': sample_results,
            'uploads': uploads_for_export,
            'missing_projects': sorted(missing_projects),
            'summary': {
                'projects_found': len(project_lookup),
                'experiments_created': total_samples,
                'experiments_expected': total_samples_expected,
                'files_uploaded_success': total_uploaded,
                'files_attempted': total_files_attempted,
                'files_skipped': total_skipped,
            },
        }

        if any(not r.get('success') for r in sample_results) or any(not r.get('success') for r in all_upload_results):
            workflow_result['success'] = False

        if summary_format in ('json', 'tsv'):
            if summary_output == '-' or not summary_output:
                out_fh = click.get_text_stream('stdout')
                close_fh = False
            else:
                out_fh = open(summary_output, 'w', encoding='utf-8', newline='')
                close_fh = True

            try:
                if summary_format == 'json':
                    json.dump(workflow_result, out_fh, indent=2, ensure_ascii=False)
                    if summary_output == '-':
                        out_fh.write('\n')
                else:
                    sample_index = {}
                    for s in sample_results:
                        key = (s.get('project'), s.get('sample_name'))
                        sample_index[key] = s

                    fieldnames = [
                        'project',
                        'sample_name',
                        'strain_name',
                        'experiment_id',
                        'sample_id',
                        'sample_created_success',
                        'sample_error',
                        'file_path',
                        'upload_success',
                        'upload_error',
                    ]
                    writer = csv.DictWriter(out_fh, fieldnames=fieldnames, delimiter='\t')
                    writer.writeheader()

                    if uploads_for_export:
                        for u in uploads_for_export:
                            proj = u.get('project_name', '')
                            sname = (u.get('sample_name') or '').strip()
                            sres = sample_index.get((proj, sname), {})
                            writer.writerow({
                                'project': proj,
                                'sample_name': sname,
                                'strain_name': sres.get('strain_name', ''),
                                'experiment_id': sres.get('experiment_id'),
                                'sample_id': sres.get('sample_id'),
                                'sample_created_success': sres.get('success'),
                                'sample_error': sres.get('error'),
                                'file_path': u.get('file_path'),
                                'upload_success': u.get('success'),
                                'upload_error': u.get('error'),
                            })
                    else:
                        for s in sample_results:
                            writer.writerow({
                                'project': s.get('project'),
                                'sample_name': s.get('sample_name'),
                                'strain_name': s.get('strain_name', ''),
                                'experiment_id': s.get('experiment_id'),
                                'sample_id': s.get('sample_id'),
                                'sample_created_success': s.get('success'),
                                'sample_error': s.get('error'),
                                'file_path': '',
                                'upload_success': '',
                                'upload_error': '',
                            })
            finally:
                if close_fh:
                    out_fh.close()

        
    except Exception as e:
        click.echo(f"❌ Error in complete workflow: {e}", err=True)







if __name__ == '__main__':
    cli()
