import importlib.metadata
import logging
import sys
from typing import Annotated

import typer

from .api import Coveralls


log = logging.getLogger('coveralls')
APP_HELP = """Publish coverage results online via coveralls.io.

This tool makes custom reports for data generated by coverage.py package and
sends it to the coveralls.io service API.

All Python files in your coverage analysis are posted to this service along
with coverage stats, so please make sure you're not ruining your own security!

Usage patterns:
  coveralls [OPTIONS]
  coveralls debug [OPTIONS]

Example:
  $ coveralls
  Submitting coverage to coveralls.io...
  Coverage submitted!
"""
DEBUG_HELP = (
    "Debug mode doesn't send anything, just outputs json to stdout. "
    'It also forces verbose output. Please use debug mode when submitting '
    'bug reports.'
)

app = typer.Typer(
    add_completion=True,
    context_settings={'help_option_names': ['-h', '--help']},
    help=APP_HELP,
)


def _show_version(value: bool) -> None:
    if value:
        typer.echo(importlib.metadata.version('coveralls'))
        raise typer.Exit()


def _run(
    *,
    is_debug: bool,
    service: str | None,
    rcfile: str,
    basedir: str | None,
    output: str | None,
    srcdir: str | None,
    submit: str | None,
    merge: str | None,
    finish: bool,
    verbose: bool,
) -> None:
    # pylint: disable=too-complex,too-many-locals
    if is_debug:
        verbose = True

    level = logging.DEBUG if verbose else logging.INFO
    log.addHandler(logging.StreamHandler())
    log.setLevel(level)

    token_required = not is_debug and not output

    try:
        coverallz = Coveralls(
            token_required,
            config_file=rcfile,
            service_name=service,
            base_dir=basedir or '',
            src_dir=srcdir or '',
        )

        if merge:
            coverallz.merge(merge)

        if is_debug:
            log.info('Testing coveralls-python...')
            coverallz.wear(dry_run=True)
            return

        if output:
            log.info('Write coverage report to file...')
            coverallz.save_report(output)
            return

        if submit:
            with open(submit) as report_file:
                coverallz.submit_report(report_file.read())
            return

        if finish:
            log.info('Finishing parallel jobs...')
            coverallz.parallel_finish()
            log.info('Done')
            return

        log.info('Submitting coverage to coveralls.io...')
        result = coverallz.wear()

        log.info('Coverage submitted!')
        log.debug(result)
        if result:
            log.info(result.get('message'))
            log.info(result.get('url'))
    except KeyboardInterrupt:  # pragma: no cover
        log.info('Aborted')
    except Exception as e:
        log.exception('Error running coveralls: %s', e)
        sys.exit(1)


@app.callback(invoke_without_command=True)
def coveralls(
    ctx: typer.Context,
    service: Annotated[
        str | None,
        typer.Option(
            '--service',
            help='Provide an alternative service name to submit.',
        ),
    ] = None,
    rcfile: Annotated[
        str,
        typer.Option('--rcfile', help='Specify configuration file.'),
    ] = '.coveragerc',
    basedir: Annotated[
        str | None,
        typer.Option(
            '--basedir',
            help='Base directory that is removed from reported paths.',
        ),
    ] = None,
    output: Annotated[
        str | None,
        typer.Option(
            '--output',
            help="Write report to file. Doesn't send anything.",
        ),
    ] = None,
    srcdir: Annotated[
        str | None,
        typer.Option(
            '--srcdir',
            help='Source directory added to reported paths.',
        ),
    ] = None,
    submit: Annotated[
        str | None,
        typer.Option('--submit', help='Upload a previously generated file.'),
    ] = None,
    merge: Annotated[
        str | None,
        typer.Option(
            '--merge',
            help='Merge report from file when submitting.',
        ),
    ] = None,
    finish: Annotated[
        bool,
        typer.Option('--finish', help='Finish parallel jobs.'),
    ] = False,
    verbose: Annotated[
        bool,
        typer.Option(
            '-v',
            '--verbose',
            help='Print extra info, always enabled when debugging.',
        ),
    ] = False,
    version: Annotated[
        bool | None,
        typer.Option(
            '--version',
            callback=_show_version,
            help='Display the version and exit.',
            is_eager=True,
        ),
    ] = None,
) -> None:
    # pylint: disable=too-many-arguments,too-many-positional-arguments
    if ctx.invoked_subcommand is not None:
        return

    _ = version
    _run(
        is_debug=False,
        service=service,
        rcfile=rcfile,
        basedir=basedir,
        output=output,
        srcdir=srcdir,
        submit=submit,
        merge=merge,
        finish=finish,
        verbose=verbose,
    )


@app.command(help=DEBUG_HELP)
def debug(
    service: Annotated[
        str | None,
        typer.Option(
            '--service',
            help='Provide an alternative service name to submit.',
        ),
    ] = None,
    rcfile: Annotated[
        str,
        typer.Option('--rcfile', help='Specify configuration file.'),
    ] = '.coveragerc',
    basedir: Annotated[
        str | None,
        typer.Option(
            '--basedir',
            help='Base directory that is removed from reported paths.',
        ),
    ] = None,
    output: Annotated[
        str | None,
        typer.Option(
            '--output',
            help="Write report to file. Doesn't send anything.",
        ),
    ] = None,
    srcdir: Annotated[
        str | None,
        typer.Option(
            '--srcdir',
            help='Source directory added to reported paths.',
        ),
    ] = None,
    submit: Annotated[
        str | None,
        typer.Option('--submit', help='Upload a previously generated file.'),
    ] = None,
    merge: Annotated[
        str | None,
        typer.Option(
            '--merge',
            help='Merge report from file when submitting.',
        ),
    ] = None,
    finish: Annotated[
        bool,
        typer.Option('--finish', help='Finish parallel jobs.'),
    ] = False,
    verbose: Annotated[
        bool,
        typer.Option(
            '-v',
            '--verbose',
            help='Print extra info, always enabled when debugging.',
        ),
    ] = False,
    version: Annotated[
        bool | None,
        typer.Option(
            '--version',
            callback=_show_version,
            help='Display the version and exit.',
            is_eager=True,
        ),
    ] = None,
) -> None:
    # pylint: disable=too-many-arguments,too-many-positional-arguments
    _ = version
    _run(
        is_debug=True,
        service=service,
        rcfile=rcfile,
        basedir=basedir,
        output=output,
        srcdir=srcdir,
        submit=submit,
        merge=merge,
        finish=finish,
        verbose=verbose,
    )


def main(argv=None):
    command = typer.main.get_command(app)
    command.main(args=argv, prog_name='coveralls', standalone_mode=False)
