from __future__ import annotations

import importlib
import json
import logging
import os
import time
from pathlib import Path
from typing import TypedDict

import requests
from commitizen.config.base_config import BaseConfig
from commitizen.cz.conventional_commits.conventional_commits import ConventionalCommitsCz
from commitizen.cz.utils import multiple_line_breaker
from commitizen.cz.utils import required_validator
from commitizen.defaults import Questions

class CustomFormatter(logging.Formatter):
    grey = '\x1b[38;20m'
    yellow = '\x1b[33;20m'
    red = '\x1b[31;20m'
    bold_red = '\x1b[31;1m'
    reset = '\x1b[0m'
    format = '%(message)s'

    FORMATS = {
        logging.DEBUG: grey + format + reset,
        logging.INFO: grey + format + reset,
        logging.WARNING: yellow + format + reset,
        logging.ERROR: red + format + reset,
        logging.CRITICAL: bold_red + format + reset,
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)

def _configure_logging():
    logger = logging.getLogger('ksul_cz')
    logger.setLevel(logging.INFO)
    logger.propagate = False
    logger.disabled = False
    if not logger.handlers:
        ch = logging.StreamHandler()
        ch.setFormatter(CustomFormatter())
        logger.addHandler(ch)
    return logger

logger = _configure_logging()

def _parse_scope(text: str) -> str:
    return '-'.join(text.strip().split())


def _parse_subject(text: str) -> str:
    return required_validator(text.strip('.').strip(), msg='Subject is required.')

def _get_installed_version(dist_name: str) -> str | None:
    try:
        return importlib.metadata.version(dist_name)
    except importlib.metadata.PackageNotFoundError:
        return None


def _get_latest_pypi_version(project: str) -> str | None:
    url = f"https://pypi.org/pypi/{project}/json"
    try:
        resp = requests.get(url, timeout=1.5)
        payload = resp.json()
        return payload.get('info', {}).get('version')
    except Exception as e:
        logger.error(f"Failed to get latest version: {e}")
        return None


def _cache_dir() -> Path:
    root = os.environ.get('XDG_CACHE_HOME')
    if root:
        return Path(root) / 'ksul_cz'
    return Path.home() / '.cache' / 'ksul_cz'


def _read_cached_latest(project: str, ttl_seconds: int) -> str | None:
    path = _cache_dir() / f"pypi-{project}.json"
    try:
        raw = path.read_text(encoding='utf-8')
        payload = json.loads(raw)
        cached_at = float(payload.get('cached_at', 0))
        latest = payload.get('latest')
        if not latest:
            return None
        if (time.time() - cached_at) <= ttl_seconds:
            return str(latest)
        return None
    except (OSError, ValueError, TypeError, json.JSONDecodeError) as e:
        logger.error(f"Failed to read cached latest version: {e}")
        return None


def _write_cached_latest(project: str, latest: str) -> None:
    path = _cache_dir() / f"pypi-{project}.json"
    try:
        path.parent.mkdir(parents=True, exist_ok=True)
        payload = {'cached_at': time.time(), 'latest': latest}
        path.write_text(json.dumps(payload), encoding='utf-8')
    except OSError as e:
        logger.error(f"Failed to write cached latest version: {e}")
        return


def _is_outdated(installed: str, latest: str) -> bool | None:
    try:
        from packaging.version import Version  # type: ignore

        return Version(installed) < Version(latest)
    except Exception as e:
        logger.error(f"Failed to compare versions: {e}")
        if installed == latest:
            return False
        return None

class KSULCommitsAnswers(TypedDict):
    prefix: str
    scope: str
    subject: str
    body: str
    footer: str
    is_breaking_change: bool

class KSUL_CZ(ConventionalCommitsCz):
    name = 'ksul_cz'
    _version_check_done = False

    def __init__(self, config: BaseConfig):
        super().__init__(config)
        _configure_logging()

        if KSUL_CZ._version_check_done:
            return
        KSUL_CZ._version_check_done = True

        installed = (
            _get_installed_version('ksul_cz')
            or _get_installed_version(__name__.split('.', 1)[0])
        )
        if not installed:
            return

        ttl_seconds = int(os.environ.get('KSUL_CZ_VERSION_CHECK_TTL_SECONDS', '600'))
        latest = _read_cached_latest('ksul_cz', ttl_seconds)
        if latest is None:
            latest = _get_latest_pypi_version('ksul_cz')
            if latest:
                _write_cached_latest('ksul_cz', latest)

        outdated = _is_outdated(installed, latest)
        if outdated is True:
            logger.warning(
                f"[ksul_cz] A newer version of ksul_cz is available (installed={installed} --> latest={latest}).",
            )

    def questions(self) -> Questions:
        return [
            {
                'type': 'list',
                'name': 'prefix',
                'message': 'Select the type of change you are committing',
                'choices': [
                    {
                        'value': 'fix',
                        'name': 'fix: A bug fix. Correlates with PATCH in SemVer',
                        'key': 'x',
                    },
                    {
                        'value': 'feat',
                        'name': 'feat: A new feature. Correlates with MINOR in SemVer',
                        'key': 'f',
                    },
                    {
                        'value': 'docs',
                        'name': 'docs: Documentation only changes',
                        'key': 'd',
                    },
                    {
                        'value': 'style',
                        'name': (
                            'style: Changes that do not affect the '
                            'meaning of the code (white-space, formatting,'
                            ' missing semi-colons, etc)'
                        ),
                        'key': 's',
                    },
                    {
                        'value': 'refactor',
                        'name': (
                            'refactor: A code change that neither fixes '
                            'a bug nor adds a feature'
                        ),
                        'key': 'r',
                    },
                    {
                        'value': 'perf',
                        'name': 'perf: A code change that improves performance',
                        'key': 'p',
                    },
                    {
                        'value': 'test',
                        'name': ('test: Adding missing or correcting existing tests'),
                        'key': 't',
                    },
                    {
                        'value': 'build',
                        'name': (
                            'build: Changes that affect the build system or '
                            'external dependencies (example scopes: pip, docker, npm)'
                        ),
                        'key': 'b',
                    },
                    {
                        'value': 'ci',
                        'name': (
                            'ci: Changes to CI configuration files and '
                            'scripts (example scopes: GitLabCI)'
                        ),
                        'key': 'c',
                    },
                    {
                        'value': 'chore',
                        'name': (
                            'chore: Maintenance and routine tasks '
                            '(example: chore(deps): update eslint to v8.14.0)'
                        ),
                        'key': 'h',
                    },
                    {
                        'value': 'revert',
                        'name': (
                            'revert: Revert/Rollback a previous commit '
                            '(example: revert: fix: correct CSS color for button background)'
                        ),
                        'key': 'v',
                    },
                ],
            },
            {
                'type': 'input',
                'name': 'scope',
                'message': (
                    'What is the scope of this change? (class or file name): (press [enter] to skip)\n'
                ),
                'filter': _parse_scope,
            },
            {
                'type': 'input',
                'name': 'subject',
                'filter': _parse_subject,
                'message': (
                    'Write a short and imperative summary of the code changes: (lower case and no period)\n'
                ),
            },
            {
                'type': 'input',
                'name': 'body',
                'message': (
                    'Provide additional contextual information about the code changes: (press [enter] to skip)\n'
                ),
                'filter': multiple_line_breaker,
            },
            {
                'type': 'confirm',
                'name': 'is_breaking_change',
                'message': 'Is this a BREAKING CHANGE? Correlates with MAJOR in SemVer',
                'default': False,
            },
            {
                'type': 'input',
                'name': 'footer',
                'message': (
                    'Footer. Information about Breaking Changes and '
                    'reference issues that this commit closes: (press [enter] to skip)\n'
                ),
            },
        ]
