import logging
from textwrap import dedent, indent
from typing import Any, Dict, Optional, TextIO, List

from msggen.model import Service, TypeName, CompositeField, EnumField, Hook
from msggen.gen.generator import IGenerator
from msggen.gen.rpc.rust import gen_composite
from msggen.gen.grpc.proto import gather_subfields


class HookGenerator(IGenerator):
    def __init__(self, dest: TextIO, meta: Dict[str, Any]):
        self.dest = dest
        self.meta = meta
        self.logger = logging.getLogger(__name__)
        self.meta = meta

    def write(self, text: str, numindent: Optional[int] = None) -> None:
        raw = dedent(text)
        if numindent is not None:
            raw = indent(text, "    " * numindent)

        self.dest.write(raw)

    def gather_hook_types(self, hooks: List[Hook]):
        """Gather all types that might need to be defined
        to represent hooks
        """
        types = []
        for hook in hooks:
            types.extend([hook.request, hook.response])
            for field in hook.request.fields:
                types.extend(gather_subfields(field))
            for field in hook.response.fields:
                types.extend(gather_subfields(field))
        return types

    def enumvar2number(self, typename: TypeName, variant):
        """Find an existing variant number of generate a new one.

        If we don't have a variant number yet we'll just take the
        largest one assigned so far and increment it by 1."""

        typename = str(typename.name)

        m = self.meta["rpc-only-enum-map"]
        variant = str(variant)
        if typename not in m:
            m[typename] = {}

        variants = m[typename]
        if variant in variants:
            return variants[variant]

        # Now find the maximum and increment once
        n = max(variants.values()) if len(variants) else -1

        m[typename][variant] = n + 1
        return m[typename][variant]

    def generate_enum(self, service: Service):
        self.write("#[derive(Clone, Debug, Deserialize, Serialize)]\n")
        self.write("pub enum Hook {\n")
        for hook in service.hooks:
            tn = hook.typename
            name = hook.name
            self.write(f'#[serde(rename = "{name}")]\n', numindent=1)
            (self.write(f"{tn}(events::{tn}Event),\n", numindent=1),)
        self.write("}\n")

    def generate_metadata(self, message: CompositeField, typename_override=None):
        if message.omit():
            return

        # If override is not specified it is a function that returns itself
        # This is equivalent to do not override
        if typename_override is None:
            typename_override = lambda x: x

        for _, f in enumerate(message.fields):
            if isinstance(f, EnumField) and not f.override():
                self.logger.debug(f"Generating enum {f}")

                for i, v in self.enumerate_enum(
                    typename_override(f.typename), f.variants
                ):
                    self.logger.debug(f"Generating enum variant {v}")

    def enumerate_enum(self, typename, variants):
        enumerated_values = [(self.enumvar2number(typename, v), v) for v in variants]
        sorted_enumerated_values = sorted(enumerated_values, key=lambda x: x[0])
        for i, v in sorted_enumerated_values:
            yield (i, v)

    def generate(self, service: Service) -> None:
        hook_fields = self.gather_hook_types(service.hooks)
        for message in [f for f in hook_fields if isinstance(f, CompositeField)]:
            self.generate_metadata(message, hook_typename_overrides)

        self.write("// This file is autogenerated by `msggen`\n")
        self.write("// Do not edit it manually, your changes will be overwritten\n\n\n")
        self.write("\n")
        self.write("use serde::{Serialize, Deserialize};\n")

        self.generate_enum(service)
        self.write("\n\n")

        self.write("pub mod events{\n")
        self.write("use crate::primitives::*;\n", numindent=1)
        self.write("use serde::{Serialize, Deserialize};\n\n", numindent=1)
        for hook in service.hooks:
            _, req_decl = gen_composite(hook.request, self.meta)
            self.write(req_decl, numindent=1)
        self.write("}\n")

        self.write("pub mod actions{\n")
        self.write("use crate::primitives::*;\n", numindent=1)
        self.write("use serde::{Serialize, Deserialize};\n\n", numindent=1)
        for hook in service.hooks:
            _, resp_decl = gen_composite(
                hook.response, self.meta, hook_typename_overrides
            )
            self.write(resp_decl, numindent=1)
        self.write("}\n")


def hook_typename_overrides(typename: str):
    # no overrides needed for now
    # if isinstance(typename, TypeName):
    #     return_class = TypeName
    # else:
    #     return_class = str
    return typename
