import os
import shutil
from typing import List, Union


def _has_same_content(path1: str, path2: str):
    if os.path.exists(path1):
        with open(path1, 'rb') as f:
            c1 = f.read()
    else:
        c1 = ''
    if os.path.exists(path2):
        with open(path2, 'rb') as f:
            c2 = f.read()
    else:
        c2 = ''
    return c1 == c2

class SyncTool:
    def __init__(self, projects: List[str]):
        self._projects = projects
        self._discrepancies = []
    def scan_dir(self, dirpath: str):
        fnames: List[str] = []
        dirnames: List[str] = []
        for p in self._projects:
            project_name = os.path.basename(p)
            fulldirpath = f'{p}/{dirpath}'.replace('$$projectname$$', project_name)
            if os.path.isdir(fulldirpath):
                for fname in os.listdir(fulldirpath):
                    if not _excluded(fname):
                        fullpath = f'{fulldirpath}/{fname}'
                        if os.path.isfile(fullpath):
                            fnames.append(fname)
                        elif os.path.isdir(fullpath):
                            dirnames.append(fname.replace(project_name, '$$projectname$$'))
        fnames = sorted(list(set(fnames)))
        dirnames = sorted(list(set(dirnames)))
        for fname in fnames:
            relpath = f'{dirpath}/{fname}'
            fullpaths: List[str] = []
            for p in self._projects:
                project_name = os.path.basename(p)
                fullpath = f'{p}/{relpath}'.replace('$$projectname$$', project_name)
                # if os.path.isfile(fullpath):
                fullpaths.append(fullpath)
            # if len(fullpaths) > 1:
            fullpaths = [a for a in fullpaths if not _file_is_jinjaroot_generated(a)]
            groups = _split_into_groups(fullpaths)
            newest_path = _get_newest_path(fullpaths)
            if len(groups) > 1:
                self._discrepancies.append({'relpath': relpath, 'groups': groups, 'newest_path': newest_path})
        for dirname in dirnames:
            relpath = f'{dirpath}/{dirname}'
            self.scan_dir(relpath)
    def report(self, *, ask: bool):
        for discr in self._discrepancies:
            print('***********************************************************')
            print(f'*** {discr["relpath"]}')
            groups = discr['groups']
            newest_path = discr['newest_path']
            something_missing = False
            for g in groups:
                for f in g:
                    x: List[str] = []
                    if not os.path.exists(f):
                        x.append('missing')
                        something_missing = True
                    if f == newest_path:
                        x.append('newest')
                    if len(x) == 0:
                        print(f)
                    else:
                        print(f'{f} ({", ".join(x)})')
                print('')
            if ask:
                print('')
                if something_missing:
                    reply = _prompt('What would you like to do? s=skip, n=use newest, d=delete all: ', ['s', 'n', 'd'])
                else:
                    reply = _prompt('What would you like to do? s=skip, n=use newest: ', ['s', 'n'])
                if reply == 's':
                    pass
                elif reply == 'n':
                    for g in groups:
                        for f in g:
                            if f != newest_path:
                                print(f'Copying {newest_path} {f}')
                                shutil.copyfile(newest_path, f)
                elif reply == 'd':
                    for g in groups:
                        for f in g:
                            if os.path.exists(f):
                                if _prompt(f'Delete {f}? ', ['y', 'n']) == 'y':
                                    print(f'Deleting {f}')
                                    os.unlink(f)
            print('')

def _prompt(question: str, valid_answers: List[str]):
    answer = None
    while answer not in valid_answers:
        answer = str(input(question))
    return answer

def _get_newest_path(paths: List[str]):
    ret = ''
    newest_mtime: Union[float, None] = None
    for path in paths:
        if os.path.exists(path):
            mtime = os.stat(path).st_mtime
            if newest_mtime is None or (mtime > newest_mtime):
                newest_mtime = mtime
                ret = path
    return ret

def _file_is_jinjaroot_generated(path: str):
    if not os.path.exists(path):
        return False
    with open(path, 'rb') as f:
        c = f.read()
    return (b'This file was automatically generated by jinjaroot.' in c) or (b'jinjaroot synctool exclude' in c)

def _split_into_groups(paths: List[str]):
    groups: List[List[str]] = []
    for path in paths:
        found = False
        for group in groups:
            if not found:
                if _has_same_content(group[0], path):
                    group.append(path)
                    found = True
        if not found:
            groups.append([path])
    return groups

def _excluded(fname: str):
    if fname.endswith('.egg-info'): return True
    if fname.endswith('.gen.ts'): return True
    return fname in ['__pycache__', 'node_modules', '.git', '.vercel', 'tmp', 'yarn.lock', 'package.json', 'jinjaroot.yaml']

def synctool(projects, *, ask: bool):
    s = SyncTool(projects)
    s.scan_dir('.')
    s.report(ask=ask)
