"""
Esup-Pod video recorder command.

Useful to check the directory DEFAULT_RECORDER_PATH,
and its subdirectories, if they contain videos that were published by recorders.
The idea is to have one directory for each recorder.
This command manage also the directory /media/opencast-files which contains the videos
generated by the Opencast Studio (see USE_OPENCAST_STUDIO).
Example: at the Montpellier university, there is one Multicam recorder for Polytech.
In settings, we have:
- DEFAULT_RECORDER_PATH (in settings_local.py) =/data/www/%userpod%/media/uploads/
- in Pod database(cf. Pod administration), recorder_recorder table: a line for the
Polytech Multicam recorder, with value 'polytech' for 'directory' property
- FTP Directory (directly on the recorder settings):
/data/www/%userpod%/media/uploads/polytech or empty (depends on vsftpd config).
So, the recorder makes a publication, via FTP, on the directory
/data/www/%userpod%/media/uploads/polytech and this script checks regularly this
directory. When video files are published, depends on recorder configuration,
an email (with a publication link) can be send to recorder's manager, or video files
can be claimed by the users.
For the Studio, some intermediate video files can be generated (for cut or merge videos)
and processed by these command.
To have more details, see online documentation:
https://www.esup-portail.org/wiki/x/DgB8Lw

This script must be executed regularly (for an example, with a CRON task).
Example: crontab -e */5 * * * * /usr/bin/bash -c '
export WORKON_HOME=/data/www/%userpod%/.virtualenvs;
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3.6;
cd /data/www/%userpod%/django_projects/podv3;
source /usr/bin/virtualenvwrapper.sh; workon django_pod;
python manage.py recorder checkDirectory'
"""

import os
import shutil
from django.utils import translation
from django.core.management.base import BaseCommand
from django.conf import settings
from django.urls import reverse
from pod.recorder.models import Recorder, Recording, RecordingFileTreatment
from pod.recorder.utils import add_comment
import hashlib
import requests
from django.core.mail import mail_admins
from django.utils import timezone
from django.contrib.sites.shortcuts import get_current_site
from pod.video.models import Video, get_storage_path_video
from pod.video_encode_transcript import encode
import datetime

LANGUAGE_CODE = getattr(settings, "LANGUAGE_CODE", "fr")

# Mediacourse directory
DEFAULT_RECORDER_PATH = getattr(settings, "DEFAULT_RECORDER_PATH", "/data/ftp-pod/ftp/")

# Site address
RECORDER_BASE_URL = getattr(settings, "RECORDER_BASE_URL", "https://pod.univ.fr")

VIDEO_ALLOWED_EXTENSIONS = getattr(
    settings,
    "VIDEO_ALLOWED_EXTENSIONS",
    (
        "3gp",
        "avi",
        "divx",
        "flv",
        "m2p",
        "m4v",
        "mkv",
        "mov",
        "mp4",
        "mpeg",
        "mpg",
        "mts",
        "wmv",
        "mp3",
        "ogg",
        "wav",
        "wma",
        "webm",
        "ts",
    ),
)

# Allow users to claim a recording made by specifics recorders
ALLOW_MANUAL_RECORDING_CLAIMING = getattr(
    settings, "ALLOW_MANUAL_RECORDING_CLAIMING", False
)
# Debug mode (0: False, 1: True)
DEBUG = getattr(settings, "DEBUG", False)

RECORDER_SELF_REQUESTS_PROXIES = getattr(
    settings,
    "RECORDER_SELF_REQUESTS_PROXIES",
    {
        "http": None,
        "https": None,
    },
)
RECORDER_ALLOW_INSECURE_REQUESTS = getattr(
    settings, "RECORDER_ALLOW_INSECURE_REQUESTS", False
)

# Encode video
ENCODE_VIDEO = getattr(settings, "ENCODE_VIDEO", "start_encode")

# Use of OpenCast studio
# Recorder directory must be opencast-files in the Recorder administration for the studio
USE_OPENCAST_STUDIO = getattr(settings, "USE_OPENCAST_STUDIO", False)
# Directory that contains files generated by the Studio
opencastMediaDir = os.path.join(settings.MEDIA_ROOT, "opencast-files")


def print_if_debug(str) -> None:
    if DEBUG:
        print(str)


def case_full_upload(
    html_message_error,
    message_error,
    recorder,
    source_file,
    filename,
    file_size,
):
    print_if_debug(" - This video wasn't already processed. Starting the process.")
    # Generation of the hashkey, depending on the IP address of the recorder
    m = hashlib.md5()
    m.update(recorder.ipunder().encode("utf-8") + recorder.salt.encode("utf-8"))

    if recorder.recording_type == "studio":
        # Management of video files generated by the Studio
        html_message_error, message_error = case_studio_recording(
            html_message_error, message_error, recorder, source_file, filename
        )
    else:
        if recorder.user is None:
            # Manual claiming
            html_message_error, message_error = case_no_manager(
                html_message_error, message_error, recorder, source_file
            )
        else:
            # Manager
            html_message_error, message_error = case_manager_exist(
                html_message_error,
                message_error,
                recorder,
                source_file,
                filename,
                file_size,
                m,
            )
    return html_message_error, message_error


def case_manager_exist(
    html_message_error,
    message_error,
    recorder,
    source_file,
    filename,
    file_size,
    m,
):
    # Generation of the URL to notify the mediacourse recorder's
    # manager, of the form:
    # https://pod.univ.fr/mediacourses_notify/?recordingPlace
    # =192_168_1_10&mediapath=file.zip&key
    # =77fac92a3f06d50228116898187e50e5
    recorder_notify = reverse("record:recorder_notify")
    urlNotify = "".join(
        [
            RECORDER_BASE_URL,
            recorder_notify,
            "?recordingPlace="
            + recorder.ipunder()
            + "&mediapath="
            + source_file
            + "&key="
            + m.hexdigest()
            + "&course_title="
            + filename,
        ]
    )
    print_if_debug(
        "- Generate the URL, with haskey, to notify the "
        "mediacourse recorder's manager: " + urlNotify
    )
    # Make a request on this URL
    try:
        request = requests.get(urlNotify, proxies=RECORDER_SELF_REQUESTS_PROXIES)
    except Exception:
        if RECORDER_ALLOW_INSECURE_REQUESTS:
            request = requests.get(
                urlNotify, proxies=RECORDER_SELF_REQUESTS_PROXIES, verify=False
            )
        else:
            certif_err = (
                "The request on recorder_notfy cannot be complete."
                "It may be a certificate issue. If you want to ignore"
                "the verification of the SSl certificate set"
                "RECORDER_ALLOW_INSECURE_REQUESTS to True"
            )
            message_error += certif_err
            print_if_debug(certif_err)
            return html_message_error, message_error
    # If all (arguments,...) are good, an email is sent to
    # mediacourse recorder's manager that a video was published
    if str(request.content)[1:] == "'ok'":
        # Email was sent. Job is done
        print_if_debug(
            "- Request was made to URL with success. An "
            "email was sent to mediacourse recorder's "
            "manager."
        )
        # Save this information in the database, to avoid to
        # send multiple emails
        RecordingFileTreatment.objects.filter(
            file=source_file,
        ).update(file_size=file_size, email_sent=True, date_email_sent=timezone.now())
        print_if_debug(
            "- Information saved in the recorder_recordingfiletreatment table."
        )
    elif str(request.content)[1:] == "'auto'":
        # Video auto published
        print_if_debug("The video was published and attributed.")
    else:
        # Email wasn't sent, due to an error
        print_if_debug(
            " - Request was made to URL with failure("
            + str(request.content)[1:]
            + "). An email wasn't sent to "
            "mediacourse recorder's "
            "manager."
        )
        # Catch the the error encountered
        html_message_error += "<li><b>Error</b>: Security error"
        (
            " for the file "
            + source_file
            + ": <b>"
            + str(request.content)[1:]
            + "</b>.<br><i>>>>Check the publish link: "
            + urlNotify
            + "</i></li> "
        )
        message_error += (
            "\n Error: Security error for the file "
            + source_file
            + ": "
            + str(request.content)[1:]
            + ".\n   >>> Check the publish link: "
            + urlNotify
        )
    return html_message_error, message_error


def case_no_manager(html_message_error, message_error, recorder, source_file):
    # Raise error and send mail to admin if a recorder has no
    # manager and manual claiming is disabled
    if not ALLOW_MANUAL_RECORDING_CLAIMING:
        html_message_error += (
            "<li><b>Error</b>: No manager "
            "for the recorder" + recorder.name + "<br><i>>>> You must assign a "
            "user for this recorder"
            " or set the"
            " ALLOW_MANUAL_RECORDING"
            "_CLAIMING "
            "setting to True</i></li> "
        )
        message_error += (
            "\n Error: No manager for the "
            "recorder " + recorder.name + "\n   "
            ">>> You need to assign a user for this"
            " recorder or set the ALLOW_MANUAL"
            "_RECORDING_CLAIMING setting to True."
        )
        print_if_debug(
            "\n\n*** An email recorder job [Error(s) "
            "encountered] was sent to Pod admins, "
            "with message: ***" + message_error
        )
        mail_admins(
            "Mediacourse job [Error(s) encountered]",
            message_error,
            fail_silently=False,
            html_message=html_message_error,
        )
    else:
        RecordingFileTreatment.objects.filter(file=source_file).update(
            require_manual_claim=True
        )
        print_if_debug("- There is no manager for this recording, waiting for claiming")
    return html_message_error, message_error


def case_studio_recording(
    html_message_error, message_error, recorder, source_file, filename
):
    # A video file exists in the Studio directory: encode it
    print_if_debug(" - Encode video file generated by Studio: " + filename)

    # Search the video infos from corresponding recording
    source_xml_file = source_file.replace(".mp4", ".xml")
    recording = Recording.objects.filter(source_file=source_xml_file).first()
    if recording:
        # Set video properties
        video = Video()
        # Video title corresponds to recording title
        video.title = recording.title
        video.owner = recording.user
        video.type = recorder.type
        video.date_evt = datetime.datetime.now()
        # Video management
        storage_path = get_storage_path_video(video, os.path.basename(source_file))
        dt = str(datetime.datetime.now()).replace(":", "-")
        name, ext = os.path.splitext(os.path.basename(source_file))
        ext = ext.lower()
        # Video name
        video_name = name + "_" + dt.replace(" ", "_") + ext
        video.video = os.path.join(os.path.dirname(storage_path), video_name)
        # Move source file to destination
        os.makedirs(os.path.dirname(video.video.path), exist_ok=True)
        os.rename(source_file, video.video.path)
        video.save()

        # Add infos from recorder (see RECORDER_ADDITIONAL_FIELDS)
        # Add any additional owners
        video.additional_owners.add(*recorder.additional_users.all())
        # Private access (draft mode)
        video.is_draft = recorder.is_draft
        # Restricted access (possibly to groups or by password)
        video.is_restricted = recorder.is_restricted
        video.restrict_access_to_groups.add(*recorder.restrict_access_to_groups.all())
        video.password = recorder.password
        # Add the possible channels
        video.channel.add(*recorder.channel.all())
        # Add the possible themes
        video.theme.add(*recorder.theme.all())
        # Add any disciplines
        video.discipline.add(*recorder.discipline.all())
        # Language choice
        video.main_lang = recorder.main_lang
        # Cursus
        video.cursus = recorder.cursus
        # Tags
        video.tags = recorder.tags
        # Transcription
        if getattr(settings, "USE_TRANSCRIPTION", False):
            video.transcript = recorder.transcript
        # Licence
        video.licence = recorder.licence
        # Allow downloading
        video.allow_downloading = recorder.allow_downloading
        # Is 360
        video.is_360 = recorder.is_360
        # Disable comments
        video.disable_comment = recorder.disable_comment
        # Add sites
        video.sites.add(*recorder.sites.all())
        # Finally save
        video.save()

        msg = "\n--\n\n"
        msg += "Start encoding file %s at %s\n--\n" % (filename, datetime.datetime.now())
        add_comment(recording.id, msg)

        # Encode
        encode_video = getattr(encode, ENCODE_VIDEO)
        encode_video(video.id)
    else:
        # Data seems to be deleted from Pod database
        print_if_debug(
            " - WARNING: It seems that this recording was deleted "
            "from Pod database. "
            "source_file: " + source_xml_file
        )

    return html_message_error, message_error


def file_exist(
    file,
    file_size,
    source_file,
    recorder,
    message_error,
    html_message_error,
    filename,
):
    if file.email_sent:
        print_if_debug(
            "- An email, with the publication link, was already sent to "
            "mediacourse recorder's manager. Nothing to do. Stopping the "
            "process for this file."
        )
    elif file.require_manual_claim:
        print_if_debug(" - Recording already treated, waiting for claiming ...")
    else:
        # File size saved in database
        file_size_in_db = file.file_size
        # Check if file is complete
        if file_size > 0 and file_size > file_size_in_db:
            print_if_debug(
                "- This video was partially uploaded. Waiting for complete file."
            )
            file = RecordingFileTreatment.objects.filter(
                file=source_file,
            ).update(file_size=file_size, email_sent=False)
            # This video wasn't already processed and no mail was sent to
            # recorder's manager => Process the video
        else:
            case_full_upload(
                html_message_error,
                message_error,
                recorder,
                source_file,
                filename,
                file_size,
            )
    return html_message_error, message_error


def recorder_exist(recorder, filename, message_error, html_message_error):
    # There is a connection between the directory and a recorder
    print_if_debug(" - This video was published by '" + recorder.name + "' recorder.")

    # Absolute path of the video
    # For a recording made by the Studio
    if recorder.recording_type == "studio":
        # Search for videos in opencast-files directory
        source_file = os.path.join(opencastMediaDir, filename)
    else:
        source_file = os.path.join(DEFAULT_RECORDER_PATH, recorder.directory, filename)
    # Check if this video was already processed
    recording = Recording.objects.filter(source_file=source_file).first()
    # Check if a job was created for this file (need at least 2 pass)
    file = RecordingFileTreatment.objects.filter(file=source_file).first()

    if recorder.recording_type != "studio" and recording:
        # This video was already processed
        print_if_debug(
            "- This video was already processed. Nothing to do. Stopping "
            "the process for this file."
        )
    else:
        # Size of the existant file
        file_size = os.path.getsize(source_file)
        if file:
            html_message_error, message_error = file_exist(
                file,
                file_size,
                source_file,
                recorder,
                message_error,
                html_message_error,
                filename,
            )
        else:
            print_if_debug(" - The job is created.")
            # The job is created
            RecordingFileTreatment.objects.create(
                file=source_file,
                recorder=recorder,
                file_size=file_size,
                email_sent=False,
                type=recorder.recording_type,
            )
    return html_message_error, message_error


def process_directory(html_message_error, message_error, files, root, studio):
    for filename in files:
        # Check if extension is a good extension (videos extensions + zip)
        extension = filename.split(".")[-1]

        # Specific cases for studio directory
        # With Studio, we can have files with these extensions.
        # We don't display error message and we use then to clean regularly.
        # A clean is made for files older than 60 days, in the opencast-files.
        possible_studio_ext = ("xml", "xml_treated")
        if studio and extension in possible_studio_ext:
            # XML file
            source_file = os.path.join(opencastMediaDir, filename)
            # File last modified time
            t = os.stat(source_file)[8]
            today = datetime.datetime.today()
            filetime = datetime.datetime.fromtimestamp(t) - today
            # Check if file is older than 60 days
            if filetime.days <= -60:
                # Remove XML file
                os.remove(source_file)
                # Directory that corresponds to the XML file
                dirname_remove = filename.replace(".xml_treated", "")
                source_directory = os.path.join(opencastMediaDir, dirname_remove)
                # Remove the directory that contains videos files and cutting.smil
                if os.path.exists(source_directory):
                    shutil.rmtree(source_directory)
            # No error message
            continue

        # Name of the directory
        dirname = root.split(os.path.sep)[-1]
        print_if_debug(
            "\n*** Process the file " + os.path.join(dirname, filename) + " ***"
        )

        valid_ext = VIDEO_ALLOWED_EXTENSIONS + ("zip",)
        if not (extension in valid_ext and filename != extension):
            print_if_debug(
                " - WARNING: " + extension + " is not a valid video "
                "extension. If it should "
                "be, add it to the setting "
                "VIDEO_ALLOWED_EXTENSIONS"
            )
            continue
        # Search for the recorder corresponding to this directory
        recorder = Recorder.objects.filter(
            directory=dirname, sites=get_current_site(None)
        ).first()
        if recorder:
            html_message_error, message_error = recorder_exist(
                recorder, filename, message_error, html_message_error
            )
        else:
            # There isn't a connection between the directory and a recorder
            print_if_debug(" - No recorder found for this file.")
            # Catch the the error encountered
            html_message_error += "<li><b>Error</b>: No recorder found for "
            (
                "the file "
                + os.path.join(DEFAULT_RECORDER_PATH, dirname, filename)
                + "<br><i>>>>You "
            )
            "must create a recorder for '" + dirname + "' directory.</i></li>"
            message_error += (
                "\n   Error"
                ": No recorder found for the file: "
                + os.path.join(DEFAULT_RECORDER_PATH, dirname, filename)
            )
            "\n   >>> You must create a recorder for '" + dirname + "' directory."
    return html_message_error, message_error


class Command(BaseCommand):
    # First possible argument: checkDirectory
    args = "checkDirectory"
    help = "Check the directory and subdirectories if they contain videos "
    "published by the recorders. "
    valid_args = ["checkDirectory"]

    def add_arguments(self, parser):
        parser.add_argument("task")

    def handle(self, *args, **options):
        # Activate a fixed locale fr
        translation.activate(LANGUAGE_CODE)
        if options["task"] and options["task"] in self.valid_args:
            html_message_error = ""
            message_error = ""
            # Path the tree
            for root, dirs, files in os.walk(DEFAULT_RECORDER_PATH):
                # For standard recorders, video files are in directories/subdirectories
                html_message_error, message_error = process_directory(
                    html_message_error, message_error, files, root, False
                )
            # Path the Studio tree
            if USE_OPENCAST_STUDIO:
                # For Studio recorder, video files are only in the 1st level directory
                for root, dirs, files in os.walk(opencastMediaDir):
                    html_message_error, message_error = process_directory(
                        html_message_error, message_error, files, root, True
                    )
                    # Only 1 level
                    break
            # If there was at least one error, send an email to Pod admins
            if message_error != "":
                print_if_debug(
                    "\n\n*** An email Mediacourse recorder job [Error(s) "
                    "encountered] was sent to Pod admins, with message: "
                    "***" + message_error
                )
                mail_admins(
                    "Mediacourse job [Error(s) encountered]",
                    message_error,
                    fail_silently=False,
                    html_message=html_message_error,
                )
        else:
            print("*** Warning: you must give some arguments: %s ***" % self.valid_args)
