from __future__ import annotations

from io import StringIO
import json
import os
from typing import Any

from esphome.config import Config, _format_vol_invalid, validate_config
import esphome.config_validation as cv
from esphome.core import CORE, DocumentRange
from esphome.yaml_util import parse_yaml


def _get_invalid_range(res: Config, invalid: cv.Invalid) -> DocumentRange | None:
    return res.get_deepest_document_range_for_path(
        invalid.path, invalid.error_message == "extra keys not allowed"
    )


def _dump_range(range: DocumentRange | None) -> dict | None:
    if range is None:
        return None
    return {
        "document": range.start_mark.document,
        "start_line": range.start_mark.line,
        "start_col": range.start_mark.column,
        "end_line": range.end_mark.line,
        "end_col": range.end_mark.column,
    }


class VSCodeResult:
    def __init__(self):
        self.yaml_errors = []
        self.validation_errors = []

    def dump(self):
        return json.dumps(
            {
                "type": "result",
                "yaml_errors": self.yaml_errors,
                "validation_errors": self.validation_errors,
            }
        )

    def add_yaml_error(self, message):
        self.yaml_errors.append(
            {
                "message": message,
            }
        )

    def add_validation_error(self, range_, message):
        self.validation_errors.append(
            {
                "range": _dump_range(range_),
                "message": message,
            }
        )


def _read_file_content_from_json_on_stdin() -> str:
    """Read the content of a json encoded file from stdin."""
    data = json.loads(input())
    assert data["type"] == "file_response"
    return data["content"]


def _print_file_read_event(path: str) -> None:
    """Print a file read event."""
    print(
        json.dumps(
            {
                "type": "read_file",
                "path": path,
            }
        )
    )


def read_config(args):
    while True:
        CORE.reset()
        data = json.loads(input())
        assert data["type"] == "validate"
        CORE.vscode = True
        CORE.ace = args.ace
        f = data["file"]
        if CORE.ace:
            CORE.config_path = os.path.join(args.configuration, f)
        else:
            CORE.config_path = data["file"]

        file_name = CORE.config_path
        _print_file_read_event(file_name)
        raw_yaml = _read_file_content_from_json_on_stdin()
        command_line_substitutions: dict[str, Any] = (
            dict(args.substitution) if args.substitution else {}
        )
        vs = VSCodeResult()
        try:
            config = parse_yaml(file_name, StringIO(raw_yaml))
            res = validate_config(config, command_line_substitutions)
        except Exception as err:  # pylint: disable=broad-except
            vs.add_yaml_error(str(err))
        else:
            for err in res.errors:
                try:
                    range_ = _get_invalid_range(res, err)
                    vs.add_validation_error(range_, _format_vol_invalid(err, res))
                except Exception:  # pylint: disable=broad-except
                    continue
        print(vs.dump())
