from __future__ import annotations

import os
import tempfile
from datetime import datetime
from pathlib import Path

import numpy as np
import soundfile as sf
from PyQt6.QtCore import QUrl
from PyQt6.QtMultimedia import QAudioOutput, QMediaPlayer
from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QStackedWidget,
    QMessageBox,
    QFileDialog,
)

from ..core.settings import load_config
from ..core.whisper_engine import TranscribeOptions
from ..core.media import is_supported_media
from ..core.exporters import save_txt, save_pdf, save_docx
from ..core.recording import RecordingSession, RecordingConfig
from .pages import LoadingPage, HomePage, SettingsPage
from .workers import ModelInitWorker, TranscribeWorker, TranscriptionJob, start_in_thread


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Whiscribe")
        self.setMinimumSize(880, 560)

        self.stack = QStackedWidget()
        self.setCentralWidget(self.stack)

        self.loading = LoadingPage()
        self.home = HomePage()
        self.settings = SettingsPage()

        self.stack.addWidget(self.loading)
        self.stack.addWidget(self.home)
        self.stack.addWidget(self.settings)

        # Navigation
        self.home.settings_clicked = self.show_settings
        self.settings.back_clicked = self.show_home

        # Main actions
        self.home.upload_clicked = self.on_upload
        self.home.record_clicked = self.on_record_start

        # Recording controls
        self.home.rec_pause_clicked = self.on_record_pause
        self.home.rec_resume_clicked = self.on_record_resume
        self.home.rec_stop_clicked = self.on_record_stop
        self.home.rec_play_clicked = self.on_record_play
        self.home.rec_save_clicked = self.on_record_save_and_transcribe
        self.home.rec_discard_clicked = self.on_record_discard

        # Result actions
        self.home.copy_btn.clicked.connect(self.copy_text)
        self.home.act_txt.triggered.connect(lambda: self.download_as("txt"))
        self.home.act_pdf.triggered.connect(lambda: self.download_as("pdf"))
        self.home.act_docx.triggered.connect(lambda: self.download_as("docx"))

        self._last_text = ""
        self._threads: list = []

        # Recording
        self._rec = RecordingSession(RecordingConfig(samplerate=16000, channels=1))
        self._rec_audio: np.ndarray | None = None
        self._rec_sr: int = 16000
        self._rec_preview_wav: str | None = None

        # Playback
        self._player = QMediaPlayer(self)
        self._audio_out = QAudioOutput(self)
        self._player.setAudioOutput(self._audio_out)

        # Start model init
        self.stack.setCurrentWidget(self.loading)
        self.init_model()

    def show_home(self) -> None:
        self.stack.setCurrentWidget(self.home)

    def show_settings(self) -> None:
        self.stack.setCurrentWidget(self.settings)

    def _opts(self) -> TranscribeOptions:
        cfg = load_config()
        os.makedirs(cfg.model_dir, exist_ok=True)
        return TranscribeOptions(
            model_size=cfg.model_size,
            model_dir=cfg.model_dir,
            language=cfg.language,
            task=cfg.task,
        )

    def init_model(self) -> None:
        worker = ModelInitWorker(self._opts())
        worker.finished.connect(self._on_model_ready)
        worker.error.connect(self._on_model_error)
        thread = start_in_thread(worker)
        self._threads.append(thread)

    def _on_model_ready(self) -> None:
        self.show_home()

    def _on_model_error(self, msg: str) -> None:
        QMessageBox.critical(
            self,
            "Model init failed",
            "Could not prepare the Whisper model.\n\n"
            f"Details:\n{msg}"
        )
        self.show_home()

    # ---------------------------
    # Upload
    # ---------------------------
    def on_upload(self) -> None:
        path, _ = QFileDialog.getOpenFileName(
            self,
            "Select audio or video file",
            "",
            "Media files (*.wav *.mp3 *.m4a *.aac *.flac *.ogg *.wma *.webm *.mp4 *.mkv *.mov *.avi *.m4v);;All files (*.*)",
        )
        if not path:
            return
        if not is_supported_media(path):
            QMessageBox.warning(self, "Unsupported", "That file type is not supported.")
            return

        self._clear_results_ui()
        self.home.results_label.setText("Transcribing…")
        self.start_transcription(path)

    # ---------------------------
    # Recording flow
    # ---------------------------
    def on_record_start(self) -> None:
        self._clear_results_ui()
        self._stop_playback_and_cleanup_preview()
        try:
            self._rec.start()
            self._rec_audio = None
            self.home.enter_recording_mode()
        except Exception as e:
            QMessageBox.critical(self, "Recording error", str(e))
            self.home.exit_recording_mode()

    def on_record_pause(self) -> None:
        try:
            self._rec.pause()
            self.home.set_paused()
        except Exception as e:
            QMessageBox.critical(self, "Recording error", str(e))

    def on_record_resume(self) -> None:
        try:
            self._rec.resume()
            self.home.set_recording()
        except Exception as e:
            QMessageBox.critical(self, "Recording error", str(e))

    def on_record_stop(self) -> None:
        try:
            data, sr = self._rec.stop()
            self._rec_audio = data
            self._rec_sr = sr
            self._write_preview_wav(data, sr)
            self.home.set_stopped()
        except Exception as e:
            QMessageBox.critical(self, "Recording error", str(e))
            self.home.exit_recording_mode()

    def on_record_play(self) -> None:
        if not self._rec_preview_wav or not os.path.exists(self._rec_preview_wav):
            QMessageBox.information(self, "Nothing to play", "No recording available to play yet.")
            return
        self._player.stop()
        self._player.setSource(QUrl.fromLocalFile(self._rec_preview_wav))
        self._player.play()

    def on_record_save_and_transcribe(self) -> None:
        if self._rec_audio is None or self._rec_audio.size == 0:
            QMessageBox.warning(self, "No audio", "No recorded audio to save.")
            return

        folder = QFileDialog.getExistingDirectory(self, "Select folder to save recording")
        if not folder:
            return

        ts = datetime.now().strftime("%Y%m%d_%H%M%S")
        out_path = str(Path(folder) / f"whiscribe_recording_{ts}.wav")

        try:
            sf.write(out_path, self._rec_audio, self._rec_sr)
        except Exception as e:
            QMessageBox.critical(self, "Save failed", str(e))
            return

        self.home.exit_recording_mode()
        self.home.results_label.setText("Transcribing…")
        self.start_transcription(out_path)

    def on_record_discard(self) -> None:
        try:
            if self._rec.is_recording:
                self._rec.stop()
        except Exception:
            pass
        self._stop_playback_and_cleanup_preview()
        self._rec_audio = None
        self.home.exit_recording_mode()

    def _write_preview_wav(self, data: np.ndarray, sr: int) -> None:
        self._stop_playback_and_cleanup_preview()
        tmp_dir = tempfile.mkdtemp(prefix="whiscribe_rec_")
        preview = os.path.join(tmp_dir, "preview.wav")
        sf.write(preview, data, sr)
        self._rec_preview_wav = preview

    def _stop_playback_and_cleanup_preview(self) -> None:
        self._player.stop()
        if not self._rec_preview_wav:
            return
        try:
            tmp_dir = str(Path(self._rec_preview_wav).parent)
            import shutil
            shutil.rmtree(tmp_dir, ignore_errors=True)
        finally:
            self._rec_preview_wav = None

    # ---------------------------
    # Transcription
    # ---------------------------
    def start_transcription(self, input_path: str) -> None:
        job = TranscriptionJob(input_path=input_path, opts=self._opts())
        worker = TranscribeWorker(job)
        worker.finished.connect(self._on_transcription_ready)
        worker.error.connect(self._on_transcription_error)
        thread = start_in_thread(worker)
        self._threads.append(thread)

    def _on_transcription_ready(self, text: str) -> None:
        self._last_text = text
        self.home.show_results(text)

    def _on_transcription_error(self, msg: str) -> None:
        QMessageBox.critical(self, "Transcription failed", msg)
        self.home.results_label.setText("")

    def _clear_results_ui(self) -> None:
        self.home.download_btn.setVisible(False)
        self.home.copy_btn.setVisible(False)
        self.home.text_box.setVisible(False)
        self.home.results_label.setText("")
        self._last_text = ""

    # ---------------------------
    # Clipboard + export
    # ---------------------------
    def copy_text(self) -> None:
        if not self._last_text:
            return
        cb = QApplication.clipboard()  # FIX: MainWindow has no clipboard(); use QApplication
        cb.setText(self._last_text)
        QMessageBox.information(self, "Copied", "Transcription copied to clipboard.")

    def download_as(self, fmt: str) -> None:
        if not self._last_text:
            return

        if fmt == "txt":
            path, _ = QFileDialog.getSaveFileName(self, "Save as TXT", "transcription.txt", "Text (*.txt)")
            if path:
                save_txt(self._last_text, path)
        elif fmt == "pdf":
            path, _ = QFileDialog.getSaveFileName(self, "Save as PDF", "transcription.pdf", "PDF (*.pdf)")
            if path:
                save_pdf(self._last_text, path)
        elif fmt == "docx":
            path, _ = QFileDialog.getSaveFileName(self, "Save as DOCX", "transcription.docx", "Word (*.docx)")
            if path:
                save_docx(self._last_text, path)
        else:
            QMessageBox.warning(self, "Unknown format", fmt)
