import os
import difflib
import subprocess
import types
import typing
from decimal import Decimal
from datetime import datetime
from django.db.models.query import QuerySet
from django.utils import timezone
from rest_framework import serializers

__serializers = set()

TYPEMAP = {
    int: 'number',
    float: 'number',
    Decimal: 'number',
    datetime: 'string',
    str: 'string',
    bool: 'boolean',
    type(None): 'null',
    QuerySet: 'number[]',
    # FIXME: we need to check the dict type and convert properly
    # Now we only support string: string
    dict: '{ [key: string]: string }',
}

def interface_name(Serializer):
    return Serializer.__name__.replace('Serializer', 'Type')


def export_interface(Serializer) -> None:
    """Mark this serializer so that its interface will be exported"""
    if issubclass(Serializer, serializers.Serializer):
        __serializers.add(_key(Serializer))


def ts_exported(Serializer) -> bool:
    """Check if a serializer interface should be exported as TS"""
    return _key(Serializer) in __serializers


def _key(Serializer):
    return '.'.join([Serializer.__module__, Serializer.__name__])

def header(app, *middle):
    now = timezone.now().strftime('%Y-%m-%d %H:%M')
    tz = timezone.now().tzinfo

    header = [
        f' * Generated by RxDjango on {now} ({tz})',
    ] + [
        f' * {line}' for line in middle
    ]

    footer = [
        f' * To rebuild it, run:',
        f' *     ./manage.py makefrontend [{app}]',
    ]

    warning = 'DO NOT EDIT THIS FILE'
    # make it fancy

    length = max([len(line) for line in header])
    w_left = int((length - len(warning)) / 2)
    w_right = length - len(warning) - w_left - 1

    header = [
        line + ' ' * (length - len(line) + 1) + '*'
        for line in header
    ]
    header.append(
        ' *' + ' ' * w_left + warning + ' ' * w_right + '*',
    )

    footer = [
        line + ' ' * (length - len(line) + 1) + '*'
        for line in footer
    ]

    code = [
        '/*' + '*' * length
    ]
    for line in header:
        code.append(line)
        code.append(
            ' *' + ' ' * (length - 1) + '*',
        )

    for line in footer:
        code.append(line)
        code.append(
            ' *' + ' ' * (length - 1) + '*',
        )

    code.append(
        ' ' + '*' * length + '*/'
    )

    return code


def diff(existing, content, filename):
    if len(existing) > 1:
        existing[0] = existing[1] = content[0] = content[1]

    filename = _git_relative_path(filename)

    fromfile = os.path.join('a', filename)
    tofile = os.path.join('b', filename)
    dif = difflib.unified_diff(existing, content, fromfile=fromfile, tofile=tofile)
    dif = list(dif)
    header, body = dif[:3], dif[3:]
    return ''.join(header) + '\n'.join(body)


def _git_relative_path(filepath):
    try:
        # Run the git command to get the relative path from the repository root
        git_root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).strip().decode('utf-8')
        relative_path = os.path.relpath(filepath, git_root).strip()
        return relative_path
    except subprocess.CalledProcessError:
        # If the git command fails, return the original path
        return filepath

    
def get_ts_type(ftype):
    if type(ftype) is types.UnionType :
        py_types = typing.get_args(ftype)
    else:
        py_types = [ftype]

    ts_types = [ TYPEMAP[typ] for typ in py_types ]
    return ' | '.join(ts_types)


def snake_to_camel(s):
    components = s.split('_')
    # We capitalize the first letter of each component except the first one
    # with the 'title' method and join them together.
    return components[0] + ''.join(x.title() for x in components[1:])

