import os
import re
import subprocess

from django.conf import settings
from django.db import models
from django.utils.encoding import force_str


class Project(models.Model):
    title = models.CharField(max_length=50)
    slug = models.SlugField()

    def __str__(self):
        return self.title


class Platform(models.Model):
    project = models.ForeignKey('Project', on_delete=models.CASCADE)
    title = models.CharField(max_length=50)
    slug = models.SlugField()
    order = models.PositiveIntegerField()

    class Meta:
        ordering = ['order']

    def __str__(self):
        return '%s / %s' % (self.project.title, self.title)


class Service(models.Model):
    project = models.ForeignKey('Project', on_delete=models.CASCADE)
    title = models.CharField(max_length=50)
    slug = models.SlugField()

    def __str__(self):
        return '%s / %s' % (self.project.title, self.title)

    def get_modules(self, platforms=None):
        return [x[0] for x in self.get_modules_with_version(platforms)]

    def get_modules_with_version(self, platforms=None):
        modules = {}  # name => (module, {platform.id: version})
        if platforms is None:
            platforms = self.project.platform_set.all()

        for platform in platforms:
            platform_modules = Module.objects.raw(
                """
                WITH all_modules AS (
                    SELECT
                        pm.id,
                        pm.name,
                        (SELECT
                            version
                        FROM projects_version pv
                        JOIN projects_installedversion piv on piv.version_id = pv.id
                        JOIN projects_installedservice pis on pis.id = piv.service_id and pis.platform_id = %s and pis.service_id = %s
                        WHERE pv.module_id = pm.id
                        ORDER BY timestamp desc LIMIT 1) as version
                    FROM projects_module pm
                )
                SELECT id, name, version AS platform_service_version
                FROM all_modules
                WHERE version <> '';
                """,
                [platform.id, self.id],
            )
            for module in platform_modules:
                if not (module.name in modules):
                    modules[module.name] = (module, {platform.id: module.platform_service_version})
                else:
                    modules[module.name][1][platform.id] = module.platform_service_version

        return sorted(modules.values(), key=lambda x: x[0].name)

    def get_installed_service(self, platform):
        try:
            installed_service = InstalledService.objects.get(service=self, platform=platform)
        except InstalledService.DoesNotExist:
            return None
        return installed_service


class InstalledService(models.Model):
    platform = models.ForeignKey('Platform', on_delete=models.CASCADE)
    service = models.ForeignKey('Service', on_delete=models.CASCADE)
    url = models.URLField()
    disabled = models.BooleanField(default=False)

    def __str__(self):
        return '%s / %s / %s (@ %s)' % (
            self.platform.project.title,
            self.platform.title,
            self.service.title,
            self.url,
        )


class Module(models.Model):
    name = models.CharField(max_length=50)
    repository_url = models.URLField(blank=True)

    def __str__(self):
        return self.name

    @property
    def redmine_project(self):
        mapping = {
            'authentic2': 'authentic',
            'python-django-mellon': 'django-mellon',
            'godo.js': 'godo',
        }
        return mapping.get(self.name) or self.name

    def get_installed_version(self, platform, service):
        try:
            installed_service = InstalledService.objects.get(platform=platform, service=service)
        except InstalledService.DoesNotExist:
            return None
        try:
            v = InstalledVersion.objects.filter(service=installed_service, version__module=self).order_by(
                '-timestamp'
            )[0]
        except IndexError:
            return None
        return v

    def get_git_log(self):
        kws = {}
        kws['stdout'] = subprocess.PIPE
        kws['stderr'] = subprocess.STDOUT
        kws['cwd'] = os.path.join(settings.MEDIA_ROOT, 'src', self.name)
        cmd = ['git', 'log', '--cherry-pick', '--format=oneline']
        with subprocess.Popen(cmd, **kws) as p:
            stdout = p.communicate()[0]
            p.wait()
        return [force_str(x).split(' ', 1) for x in stdout.splitlines()]

    def get_version_hash(self, version_number):
        if re.findall(r'\.g([0-9a-f]{7,12})', version_number):
            return re.findall(r'\.g([0-9a-f]{7,12})', version_number)[0]
        if re.findall(r'\+g([0-9a-f]{7,12})', version_number):
            return re.findall(r'\+g([0-9a-f]{7,12})', version_number)[0]
        if re.findall(r'\-g([0-9a-f]{7,12})', version_number):
            return re.findall(r'\-g([0-9a-f]{7,12})', version_number)[0]
        tagname = 'v' + version_number
        kws = {}
        kws['stdout'] = subprocess.PIPE
        kws['stderr'] = subprocess.STDOUT
        kws['cwd'] = os.path.join(settings.MEDIA_ROOT, 'src', self.name)
        cmd = ['git', 'rev-list', '-1', tagname]
        with subprocess.Popen(cmd, **kws) as p:
            stdout = p.communicate()[0]
            p.wait()
        return stdout[:7].decode('ascii')

    def get_diff_log(self, v1, v2, format='oneline', grep=None):
        kws = {}
        kws['stdout'] = subprocess.PIPE
        kws['stderr'] = subprocess.STDOUT
        kws['cwd'] = os.path.join(settings.MEDIA_ROOT, 'src', self.name)
        cmd = ['git', 'log', '--left-right', '--cherry-pick', '--format=%s' % format]
        if grep:
            cmd.append('--grep')
            cmd.append(grep)

        def get_ref(version_number):
            if re.findall(r'\.g([0-9a-f]{7,12})', version_number):
                return re.findall(r'\.g([0-9a-f]{7,12})', version_number)[0]
            if re.findall(r'\+g([0-9a-f]{7,12})', version_number):
                return re.findall(r'\+g([0-9a-f]{7,12})', version_number)[0]
            if re.findall(r'\-g([0-9a-f]{7,12})', version_number):
                return re.findall(r'\-g([0-9a-f]{7,12})', version_number)[0]
            return 'v' + version_number

        cmd.append('%s...%s' % (get_ref(v1), get_ref(v2)))

        with subprocess.Popen(cmd, **kws) as p:
            stdout = p.communicate()[0]
            p.wait()
        return stdout.decode('utf-8')

    def get_all_installed_versions(self):
        versions = set()
        for service in InstalledService.objects.all():
            version = self.get_installed_version(service.platform, service.service)
            if version and version.version.version:
                versions.add(version.version.version)
        return versions


class Version(models.Model):
    module = models.ForeignKey('Module', on_delete=models.CASCADE)
    version = models.CharField(max_length=500, blank=True)

    def __str__(self):
        return '%s %s' % (self.module.name, self.version)


class InstalledVersion(models.Model):
    service = models.ForeignKey('InstalledService', on_delete=models.CASCADE)
    version = models.ForeignKey('Version', on_delete=models.CASCADE)
    timestamp = models.DateTimeField()

    def get_previous_version(self):
        try:
            return InstalledVersion.objects.filter(
                service=self.service, version__module=self.version.module, timestamp__lt=self.timestamp
            ).order_by('-timestamp')[0]
        except IndexError:
            return None


ISSUE_TYPES = [
    ('NEW', 'NEW (Nouveautés)'),
    ('BUGFIX', 'BUGFIX (Corrections)'),
    ('DEV', 'DEV (Développements)'),
    ('TECH', 'TECH (À ignorer)'),
]

PAID_BY_TYPES = [
    ('own-funds', 'Fonds propres'),
    ('client', 'Financement client'),
    ('both', 'Mix de fonds propres et financement client'),
    ('other', 'Autre'),
    ('n-a', 'Pas applicable'),
]

ORIGIN_TYPES = [
    ('client', 'Projet client'),
    ('roadmap', 'Roadmap (collaboration CPF-CPT)'),
    ('mutu', 'Développement mutualisé'),
    ('initiative', 'Initiative perso'),
    ('other', 'Autre'),
]


class IssueInfo(models.Model):
    issue_id = models.IntegerField()
    issue_type = models.CharField('Type de ticket', max_length=10, choices=ISSUE_TYPES, null=True, blank=True)
    doc_focus = models.BooleanField('Attention à porter à la doc (la mettre à jour ?)', default=False)
    wording = models.TextField('Description pour les notes de mise à jour', blank=True)
    origin = models.CharField('Initiative', max_length=100, choices=ORIGIN_TYPES, blank=True)
    paid_by = models.CharField('Financé par', max_length=100, choices=PAID_BY_TYPES, blank=True)
