from pathlib import Path
from typing import Annotated, Optional, cast

from cognite.client import ClientConfig, CogniteClient
from cognite.client.credentials import OAuthClientCredentials
from cognite.client.exceptions import CogniteAPIError

from cognite import pygen
from cognite.pygen import generate_sdk
from cognite.pygen._settings import PygenSettings, load_settings

try:
    import typer
except ImportError:
    _has_typer = False
else:
    _has_typer = True


def _version_callback(value: bool):
    if value:
        typer.echo(pygen.__version__)
        raise typer.Exit()


if _has_typer:
    app = typer.Typer(add_completion=False)

    @app.callback()
    def common(
        ctx: typer.Context,
        version: bool = typer.Option(None, "--version", callback=_version_callback),
    ): ...

    settings = load_settings()
    help_text = "Generate a Python SDK from Data Model(s)"
    if settings is not None:
        loaded_settings = cast(PygenSettings, settings)

        @app.command(help=help_text)
        def generate(
            space: str = typer.Option(default=loaded_settings.space.default, help=loaded_settings.space.help),
            external_id: str = typer.Option(
                default=loaded_settings.external_id.default, help=loaded_settings.external_id.help
            ),
            version: str = typer.Option(default=loaded_settings.version.default, help=loaded_settings.version.help),
            tenant_id: str = typer.Option(
                default=loaded_settings.tenant_id.default,
                help=loaded_settings.tenant_id.help,
            ),
            token_url: str = typer.Option(
                default=loaded_settings.token_url.default, help=loaded_settings.token_url.help
            ),
            scopes: str = typer.Option(default=loaded_settings.scopes.default, help=loaded_settings.scopes.help),
            audience: str = typer.Option(default=loaded_settings.audience.default, help=loaded_settings.audience.help),
            client_id: str = typer.Option(
                default=loaded_settings.client_id.default, help=loaded_settings.client_id.help
            ),
            client_secret: str = typer.Option(
                default=loaded_settings.client_secret.default,
                help=loaded_settings.client_secret.help,
                show_default=False,
            ),
            cdf_cluster: str = typer.Option(
                default=loaded_settings.cdf_cluster.default, help=loaded_settings.cdf_cluster.help
            ),
            cdf_url: Optional[str] = typer.Option(
                default=loaded_settings.cdf_url.default, help=loaded_settings.cdf_url.help
            ),
            cdf_project: str = typer.Option(
                default=loaded_settings.cdf_project.default, help=loaded_settings.cdf_project.help
            ),
            output_dir: Path | None = typer.Option(
                default=loaded_settings.output_dir.default, help=loaded_settings.output_dir.help
            ),
            top_level_package: str = typer.Option(
                loaded_settings.top_level_package.default, help=loaded_settings.top_level_package.help
            ),
            client_name: str = typer.Option(loaded_settings.client_name.default, help=loaded_settings.client_name.help),
            instance_space: str | None = typer.Option(
                loaded_settings.instance_space.default, help=loaded_settings.instance_space.help
            ),
            overwrite: bool = typer.Option(loaded_settings.overwrite.default, help=loaded_settings.overwrite.help),
            format_code: bool = typer.Option(
                loaded_settings.format_code.default, help=loaded_settings.format_code.help
            ),
        ):
            if token_url:
                oauth_provider = OAuthClientCredentials(
                    token_url=token_url,
                    client_id=client_id,
                    client_secret=client_secret,
                    scopes=scopes.split(" ") if scopes else [],
                    audience=audience,
                )
                clientConfig = ClientConfig(
                    client_name="pygen",
                    project=cdf_project,
                    credentials=oauth_provider,
                    base_url=cdf_cluster,
                )
                client = CogniteClient(clientConfig)
            else:
                credentials = OAuthClientCredentials.default_for_azure_ad(
                    tenant_id, client_id, client_secret, cdf_cluster
                )
                clientConfig = ClientConfig(
                    client_name="pygen",
                    project=cdf_project,
                    credentials=credentials,
                    base_url=cdf_url or f"https://{cdf_cluster}.cognitedata.com",
                )
                client = CogniteClient(clientConfig)
            data_models: list[tuple[str, str, str]]
            if settings and settings.data_models:
                data_models = settings.data_models
            else:
                data_models = []
            if space is not None and external_id is not None and version is not None:
                data_models = [(space, external_id, version)]
            if not data_models:
                raise ValueError(
                    "No data model(s) specified. Please either with --space --external-id --version "
                    "or 'data_models' in the 'pyproject.toml' file."
                )
            try:
                generate_sdk(
                    data_models,
                    client,
                    top_level_package,
                    client_name,
                    instance_space,
                    output_dir=output_dir,
                    logger=typer.echo,
                    overwrite=overwrite,
                    format_code=format_code,
                )
            except (CogniteAPIError, IndexError) as e:
                raise typer.Exit(code=1) from e

    else:
        default_settings = PygenSettings()

        @app.command(help=help_text)
        def generate(  # type: ignore[misc]
            space: Annotated[str, typer.Option(..., help=default_settings.space.help)],
            external_id: Annotated[str, typer.Option(..., help=default_settings.external_id.help)],
            version: Annotated[str, typer.Option(..., help=default_settings.version.help)],
            client_id: Annotated[str, typer.Option(..., help=default_settings.client_id.help)],
            client_secret: Annotated[str, typer.Option(..., help="Client Secret for connecting to CDF")],
            cdf_cluster: Annotated[str, typer.Option(..., help=default_settings.cdf_cluster.help)],
            cdf_project: Annotated[str, typer.Option(..., help=default_settings.cdf_project.help)],
            cdf_url: Annotated[Optional[str], typer.Option(..., help=default_settings.cdf_url.help)] = None,
            tenant_id: Annotated[Optional[str], typer.Option(..., help=default_settings.tenant_id.help)] = None,
            token_url: Annotated[Optional[str], typer.Option(..., help=default_settings.token_url.help)] = None,
            scopes: Annotated[Optional[str], typer.Option(..., help=default_settings.scopes.help)] = None,
            audience: Annotated[Optional[str], typer.Option(..., help=default_settings.audience.help)] = None,
            output_dir: Path = typer.Option(None, help=default_settings.output_dir.help),
            top_level_package: str = typer.Option(
                default_settings.top_level_package.default, help=default_settings.top_level_package.help
            ),
            client_name: str = typer.Option(
                default_settings.client_name.default, help=default_settings.client_name.help
            ),
            instance_space: str | None = typer.Option(
                default_settings.instance_space.default, help=default_settings.instance_space.help
            ),
            overwrite: bool = typer.Option(default_settings.overwrite.default, help=default_settings.overwrite.help),
            format_code: bool = typer.Option(
                default_settings.format_code.default, help=default_settings.format_code.help
            ),
        ):
            if token_url:
                oauth_provider = OAuthClientCredentials(
                    token_url=token_url,
                    client_id=client_id,
                    client_secret=client_secret,
                    scopes=scopes.split(" ") if scopes else [],
                    audience=audience,
                )
                clientConfig = ClientConfig(
                    client_name="pygen",
                    project=cdf_project,
                    credentials=oauth_provider,
                    base_url=cdf_cluster,
                )
                client = CogniteClient(clientConfig)
            elif tenant_id:
                credentials = OAuthClientCredentials.default_for_azure_ad(
                    tenant_id, client_id, client_secret, cdf_cluster
                )
                clientConfig = ClientConfig(
                    client_name="pygen",
                    project=cdf_project,
                    credentials=credentials,
                    base_url=cdf_url or f"https://{cdf_cluster}.cognitedata.com",
                )
                client = CogniteClient(clientConfig)
            else:
                print("Either tenant-id or token-url is required parameters")
                raise typer.Exit(code=1)
            try:
                generate_sdk(
                    (space, external_id, version),
                    client,
                    top_level_package,
                    client_name,
                    default_instance_space=instance_space,
                    output_dir=output_dir,
                    logger=typer.echo,
                    overwrite=overwrite,
                    format_code=format_code,
                )
            except (CogniteAPIError, IndexError) as e:
                raise typer.Exit(code=1) from e

    def main():
        app()

else:

    def main():
        print("THE CLI requires typer to be available, install with `pip install cognite-pygen[cli]`")


if __name__ == "__main__":
    main()
