#!/usr/bin/env python
import os
import sys
import time

from PyQt6.QtCore import QThreadPool
from PyQt6.QtGui import QIcon, QPixmap
from PyQt6.QtWidgets import QMainWindow, QApplication, QFileDialog

from _BitMover_MainWindow import Ui_MainWindow
from _configure import CONFIG_FILE, Configure
from _find_files import FindFiles
from _find_files_dialog import FindProgress
from _import_dialog import DialogImport
# from _media_import import MediaImporter
from _preview import MediaPreview
from _thread_my_stuff import Worker
from _media_file import MediaFile
from _file_stuff import (path_exists,
                            is_dir,
                            is_file)
from _hashing import hash_path


basedir = os.path.dirname(__file__)

# TODO: verify source dir actually exists

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow,self).__init__(*args,**kwargs)
        self.setupUi(self)
        self.setWindowTitle("BitMover")
        self.setWindowIcon(QIcon(os.path.join(basedir,'assets', 'forklift.ico')))
        self.threadpool         = QThreadPool()
        self.config = None
        self.src_dir = None
        self.dst_dir = None
        self.verify_checksum = None
        self.cleanup_files = None
        self.store_originals = None
        self.file_types = None
        self.source_path_hash = None
        self.search_types = None
        self.chunk_size = (16 * 1024) * 1
        self.load_config()

        self.destination_original_path = None
        self.path_file_source = None
        self.path_file_destination = None
        self.path_file_destination_original = None

        # File Stuff
        self.total_files        = 0
        self.file_total         = 0
        self.files              = {}

        self.event_name = None

        self.imp_dialog         = DialogImport()
        self.find_files_dialog  = FindProgress()

        self.widgets_config()

    def load_config(self):
        try:
            c = Configure(CONFIG_FILE)
            self.config = c.load_config()
            self.src_dir = self.config['folders']['source']['base']
            self.dst_dir = self.config['folders']['destination']['base']
            self.verify_checksum = self.config.get('verify_checksum', False)
            self.cleanup_files = self.config.get('cleanup_sd', False)
            self.store_originals = self.config.get('store_originals', False)
            self.file_types = self.config.get('file_types', {})
        except KeyError as e:
            log.error(f"Missing configuration key: {e}")
            sys.exit(1)  # or provide a fallback

    def widgets_config(self):
        # Button Setup
        self.pushButton_src_browse.clicked.connect(self.select_src_directory)
        self.pushButton_dst_browse.clicked.connect(self.select_dst_directory)
        self.pushButton_3_scan_dir.clicked.connect(self.find_files)
        self.pushButton_import.clicked.connect(self.import_files)

        # Initialize widgets
        self.lineEdit_src_dir.setText(self.src_dir)
        self.lineEdit_dst_dir.setText(self.dst_dir)
        self.toggle_scan_button(True)
        self.toggle_import_button(False)
        self.lcd_files_found.display(int(0))
        self.set_default_thumbnail()
        self.file_list.currentItemChanged.connect(self.index_changed)
        self.checkBox_verify_checksum.setChecked(self.verify_checksum)
        self.checkBox_cleanup_files.setChecked(self.cleanup_files)
        self.checkBox_store_originals.setChecked(self.store_originals)
        self.checkBox_verify_checksum.checkStateChanged.connect(self.verify_checksum_changed)
        self.checkBox_cleanup_files.checkStateChanged.connect(self.cleanup_files_changed)
        self.checkBox_store_originals.checkStateChanged.connect(self.store_originals_changed)
        self.clear_metadata()

        # Setup thread pool
        print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())

    def toggle_scan_button(self,enable=True):
        print(f'toggle_scan_button.enabled: {enable}')
        self.pushButton_3_scan_dir.setEnabled(enable)

    def toggle_import_button(self,enable=True):
        self.pushButton_import.setEnabled(enable)

    def select_src_directory(self):
        directory           = QFileDialog.getExistingDirectory(self,
                                                               "Select Directory",
                                                               self.src_dir)

        if directory:
            print("Selected Directory:", directory)
            # path = Path(directory)
            self.src_dir    = directory
            self.lineEdit_src_dir.setText(self.src_dir)

    def select_dst_directory(self):
        directory           = QFileDialog.getExistingDirectory(self,
                                                               "Select Directory",
                                                               self.dst_dir)

        if directory:
            print("Selected Directory:", directory)
            # path = Path(directory)
            self.dst_dir    = directory
            self.lineEdit_dst_dir.setText(self.dst_dir)

    def get_source_path_hash(self,f):
        self.source_path_hash               = hash_path(f)
        return self.source_path_hash

    def verify_checksum_changed(self):
        if self.checkBox_verify_checksum.isChecked():
            self.config['verify_checksum']  = True
        else:
            self.config['verify_checksum']  = False
        print(f"verify_checksums: {self.config['verify_checksums']}")

    def cleanup_files_changed(self):
        if self.checkBox_cleanup_files.isChecked():
            self.config['cleanup_sd']       = True
        else:
            self.config['cleanup_sd']       = False
        print(f"cleanup_sd: {self.config['cleanup_sd']}")

    def store_originals_changed(self):
        if self.checkBox_store_originals.isChecked():
            self.config['store_originals']  = True
        else:
            self.config['store_originals']  = False
        print(f"store_originals: {self.config['store_originals']}")

    def set_thumbnail(self,thumb_file,scaled=True,ratio=None):
        self.img_preview.setPixmap(QPixmap(thumb_file))
        self.img_preview.setScaledContents(scaled)
        if ratio is not None:
            self.img_preview.setFixedHeight(self.img_preview.width() / ratio)

    def set_default_thumbnail(self):
        self.set_thumbnail(os.path.join(basedir,
                                        'assets',
                                        'preview_placeholder.jpg'))

    def get_preview(self,path_file_name):
        preview = MediaPreview(path_file_name=path_file_name,
                               media_files=self.files)
        return preview

    def update_preview(self,preview):
        self.set_thumbnail(preview.thumbnail,
                           ratio=preview.thumbnail_ratio)

    def update_metadata(self,preview):
        self.clear_metadata()
        path_hash       = preview.source_path_hash
        f               = self.files[path_hash]
        f_date          = f['date']['capture_date']
        self.l_data_file_source_path.setText(
            self.files[path_hash]['folders']['source_path'])
        self.l_data_file_dest_path.setText(
            self.files[path_hash]['folders']['destination'])
        self.l_meta_content_date_time_c.setText(
            f"{f_date['y']}/{f_date['m']}/{f_date['d']}"
        )
        if f['file_type'] == 'image':
            f_photo = f['image_meta']['photo']

            self.l_meta_01.setText('Size')
            self.l_meta_02.setText('dpi')
            self.l_meta_03.setText('ISO')
            self.l_meta_04.setText('Lens')
            self.l_meta_05.setText('Focal Length')
            self.l_meta_06.setText('Camera')
            self.l_meta_07.setText('Aperture')
            self.l_meta_08.setText('Megapixels')
            self.l_meta_content_01.setText(str(f_photo['size']['width_height']))
            self.l_meta_content_02.setText(str(f_photo['dpi']))
            self.l_meta_content_03.setText(str(f_photo['iso']))
            self.l_meta_content_04.setText(str(f_photo['lens_model']))
            self.l_meta_content_05.setText(str(f_photo['lens_focal_length']))
            self.l_meta_content_06.setText(str("f_photo['camera_brand']} {f_photo['camera_model']}"))
            self.l_meta_content_07.setText(str(f_photo['aperture']))
            self.l_meta_content_08.setText(str(f_photo['size']['megapixels']))

        elif f['file_type'] == 'video':
            f_video = f['video_meta']['video']

            self.l_meta_01.setText('Size')
            self.l_meta_02.setText('Frames / Second')
            self.l_meta_03.setText('Bit Depth')
            self.l_meta_04.setText('Duration')
            self.l_meta_05.setText('Encoder')
            self.l_meta_06.setText('Codec')
            self.l_meta_07.setText('Profile')
            self.l_meta_08.setText('Pix Format')

            self.l_meta_content_01.setText(str(f_video['size']['width_height']))
            self.l_meta_content_02.setText(str(f_video['r_framerate']))
            self.l_meta_content_03.setText(str(f_video['bit_depth']))
            self.l_meta_content_04.setText(str(f_video['duration']))
            self.l_meta_content_05.setText(str(f_video['encoding']))
            self.l_meta_content_06.setText(str(f_video['codec']))
            self.l_meta_content_07.setText(str(f_video['profile']))
            self.l_meta_content_08.setText(str(f_video['pix_format']))

    def clear_metadata(self):
        self.l_meta_01.setText('')
        self.l_meta_02.setText('')
        self.l_meta_03.setText('')
        self.l_meta_04.setText('')
        self.l_meta_05.setText('')
        self.l_meta_06.setText('')
        self.l_meta_07.setText('')
        self.l_meta_08.setText('')
        self.l_meta_content_01.setText('')
        self.l_meta_content_02.setText('')
        self.l_meta_content_03.setText('')
        self.l_meta_content_04.setText('')
        self.l_meta_content_05.setText('')
        self.l_meta_content_06.setText('')
        self.l_meta_content_07.setText('')
        self.l_meta_content_08.setText('')

    def index_changed(self,i):
        self.clear_metadata()
        if i is None:
            self.set_default_thumbnail()
        else:
            print(f'index changed to: {i.text()}')
            preview = self.get_preview(i.text())
            self.update_preview(preview)
            self.update_metadata(preview)

    def get_event(self):
        self.event_name          = self.eventName.text()
        return self.event_name

    def get_search_types(self):
        self.search_types       = []
        if self.checkBox_search_for_images.isChecked():
            self.search_types.append('image')
        if self.checkBox_search_for_video.isChecked():
            self.search_types.append('video')
        if self.checkBox_search_for_audio.isChecked():
            self.search_types.append('audio')
        return self.search_types

    def get_t_files(self):
        file_total = 0
        self.file_list.clear()
        self.img_preview.setPixmap(QPixmap(os.path.join(basedir,
                                                        'assets',
                                                        'preview_placeholder.jpg')))

        for folder, subfolders, filename in os.walk(self.src_dir):
            for f_type in self.search_types:
                for ext in self.file_types[f_type]:
                    for file in filename:
                        if file.lower().endswith(ext):
                            current_file = os.path.join(folder, file)
                            if is_file(current_file):
                                file_total += int(1)
                            else:
                                print(f"Skipping {current_file} as it does not look like a real file.")
        return file_total

    def set_total_files(self,t):
        total_file_count    = t
        self.lcd_files_found.display(total_file_count)

    def set_imported_files(self,t):
        self.lcd_files_imported.display(t)

    @staticmethod
    def print_output(s):
        print(f'output: {s}')

    def worker_thread_started(self):
        print('scan thread started')
        self.toggle_scan_button(False)
        self.toggle_import_button(False)

    def worker_thread_done(self):
        print('scan thread complete.')
        self.toggle_scan_button(True)
        if 0 < len(self.files):
            self.toggle_import_button(True)
        else:
            self.toggle_import_button(False)

    @staticmethod
    def thread_complete():
        print("THREAD COMPLETE.")

    def add_found_file_to_list(self,f):
        self.file_list.addItem(f)

    def gen_file_dict(self,d):
        self.files      = d

    def process_file(self,p):
        """ gather information and add to dictionary """
        media_file = MediaFile(path_file_name=p,
                               config=self.config,
                               event_name=self.event_name)
        i = self.get_source_path_hash(p)
        print(f'processing: {p}\nhash: {i}')
        self.files[i] = media_file.media

    def find_files(self):
        """ find files to build a dictionary out of """
        self.files                  = {}
        self.set_total_files(0)
        self.get_event()
        self.get_search_types()
        self.file_total = self.get_t_files()
        self.set_total_files(self.file_total)

        time.sleep(3)

        file_finder                 = FindFiles(search_types=self.search_types,
                                                src_dir=self.src_dir,
                                                file_types=self.file_types,
                                                file_total=self.file_total)
        worker                      = Worker(file_finder.t_find_files)

        worker.signals.started.connect(
            self.worker_thread_started)
        worker.signals.started.connect(
            self.find_files_dialog.open_find_files_dialog)
        worker.signals.progress.connect(
            self.find_files_dialog.set_progress_finding_files)
        worker.signals.found_file.connect(
            self.add_found_file_to_list)
        worker.signals.found_file.connect(
            self.process_file)
        worker.signals.total_file_count.connect(
            self.set_total_files)
        # worker.signals.result.connect(
        #     self.gen_file_dict)
        worker.signals.finished.connect(
            self.thread_complete)
        worker.signals.finished.connect(
            self.worker_thread_done)
        worker.signals.finished.connect(
            self.find_files_dialog.close_find_files_dialog)

        # Execute.
        self.threadpool.start(worker)

    # From _media_import.py
    def is_video(self,f):
        if self.files[f]['type'] == 'video':
            r = True
        else:
            r = False

        return r

    def is_image(self,f):
        if self.files[f]['type'] == 'image':
            r = True
        else:
            r = False

        return r

    def t_copy_files(self,
                     import_progress_callback,
                     current_file_progress_callback,
                     imported_file_count_callback
                     ):
        """ Copy Files. """
        count               = int(0)
        for file in self.files:
            self.src_dir       = self.files[file]['folders']['source_path']
            self.dst_dir         = self.files[file]['folders']['destination']
            self.path_file_source               = path.join(self.src_dir,
                                                            self.files[file]['name'])
            self.path_file_destination               = path.join(self.dst_dir,
                                                self.files[file]['name'])
            self.destination_original_path    = path.join(self.dst_dir,
                                                self.files[file]['folders']['destination_original'])
            self.path_file_destination_original              = path.join(self.dst_dir,
                                                                         self.files[file]['folders']['destination_original'],
                                                                         self.files[file]['name'])

            self.imp_dialog.set_importing_file(self.path_file_source)

            self.copy_a_file(
                file,
                current_file_progress_callback)

            if self.check_store_original(file) is True:
                self.copy_a_file(file,
                                 current_file_progress_callback)

            count += 1

            imported_file_count_callback.emit(count)
            import_progress_callback.emit(round((count / self.file_total) * 100, 1))
            self.imp_dialog.add_to_imported_list(self.path_file_source)

    def copy_a_file(self,
                    _f,
                    current_file_progress_callback):

        size = path.getsize(self.path_file_source)
        create_folder(self.dst_dir)

        self.check_duplicate(_f)

        if self.is_video(_f):
            self.chunk_size = (1024 * 1024) * 5
        else:
            self.chunk_size = (16 * 1024) * 1

        with open(self.path_file_source, 'rb') as fs:
            with open(self.path_file_destination, 'wb') as fd:
                while True:
                    chunk = fs.read(self.chunk_size)
                    if not chunk:
                        break
                    fd.write(chunk)
                    dst_size = path.getsize(self.path_file_destination)
                    current_file_progress_callback.emit(round((dst_size / size) * 100, 1))

    def check_store_original(self,_f):
        if self.config['store_originals'] is True:
            if self.is_image(_f):
                self.dst_dir = self.destination_original_path
                self.path_file_destination = self.path_file_destination_original
                r = True
            else:
                r = False
        else:
            r = False

        return r

    def check_duplicate(self,_f):
        if path_exists(self.path_file_destination):
            check_match = cmp_files(self.path_file_source, self.path_file_destination)
            if check_match is False:
                print(f'\nFound duplicate for {self.path_file_source}, renaming destination with hash appended.')
                base, extension = path.splitext(self.files[_f]['name'])
                f_xxhash = xx_hash(self.path_file_destination)
                file_name_hash = base + '_' + f_xxhash + extension
                rename(self.path_file_destination, path.join(self.dst_dir, file_name_hash))

    # END from _media_import.py

    def import_files(self):
        """
        Import found files
        """

        # Initialize Widgets
        self.lcd_files_imported.display(int(0))

        self.imp_dialog.set_progress_importing(0)
        self.imp_dialog.set_progress_current_file(0)
        worker                      = Worker(self.t_copy_files)
        worker.signals.started.connect(
            self.worker_thread_started)
        worker.signals.started.connect(
            self.imp_dialog.open_import_dialog)
        worker.signals.import_progress.connect(
            self.imp_dialog.set_progress_importing)
        worker.signals.current_file_progress.connect(
            self.imp_dialog.set_progress_current_file)
        worker.signals.imported_file_count.connect(
            self.set_imported_files)
        worker.signals.result.connect(
            self.print_output)
        worker.signals.finished.connect(
            self.thread_complete)
        worker.signals.finished.connect(
            self.worker_thread_done)

        # Execute
        self.threadpool.start(worker)

    def verify_checksum(self):
        # fh_match    = FileHash()
        print(f'verify_checksum,self.config: {self.config}')

app     = QApplication(sys.argv)

window  = MainWindow()
window.show()

app.exec()