import os
import random
import shutil
from typing import Annotated, List, Dict

import numpy as np
from fastapi import FastAPI, File, UploadFile, Depends, HTTPException
from fastapi.staticfiles import StaticFiles
from starlette import status
from starlette.requests import Request

from fastapi.templating import Jinja2Templates

import utils.postgres_vector_database
from config import paths
import logging

from model import models
from model.Lecture import LectureBase
from model.LectureTopic import LectureTopicBase
from model.response_model import ResponseModel
# from service.AbstractAudioService import AbstractAudioService
from service.AbstractOCRService import AbstractOCRService
from service.AbstractTextClassificationService import AbstractTextClassificationService
from service.AbstractTextRepresentationService import AbstractTextRepresentationService
from service.AbstractTextSummarizationService import AbstractTextSummarizationService
from service.EmbeddingAudioService import EmbeddingAudioService
from service.ImageSamplerService import ImageSamplerService
from service.LectureService import LectureService
# from service.MyAudioService import MyAudioService
from service.SimpleTextRepresentationService import SimpleTextRepresentationService
from service.SimpleTextSummarizationService import SimpleTextSummarizationService
from service.TesseractOCRService import TesseractOCRService
from service.ZeroShotTextClassificationService import ZeroShotTextClassificationService

from sqlalchemy.orm import Session

from utils.postgres_database import engine, get_db

logging.basicConfig()
logging.getLogger("sqlalchemy.engine").setLevel(logging.DEBUG)
logger = logging.getLogger("uvicorn.error")

# Folders for upload and output
if not os.path.exists(paths.UPLOAD_FOLDER):
    os.makedirs(paths.UPLOAD_FOLDER, exist_ok=True)
if not os.path.exists(paths.SLIDES_OUTPUT_FOLDER):
    os.makedirs(paths.SLIDES_OUTPUT_FOLDER, exist_ok=True)
if not os.path.exists(paths.AUDIO_OUTPUT_FOLDER):
    os.makedirs(paths.AUDIO_OUTPUT_FOLDER, exist_ok=True)




templates = Jinja2Templates(directory=paths.static)
app = FastAPI()

## static files to a specific ULR
# by this a user can display an image on http://localhost:8000/static/file_name.png
app.mount("/static", StaticFiles(directory=paths.static), name="static")


## db initialization
models.Base.metadata.create_all(bind=engine)


# dependency injection
db_dependency = Annotated[Session, Depends(get_db)]

# Dependency injection for LectureService
def get_lecture_service(db: Session = Depends(get_db)):
    return LectureService(db)


## endpoint and mappings -- controller
@app.post("/api/lectures/")
async def create_lecture(lecture: LectureBase, lecture_service: LectureService = Depends(get_lecture_service)):
    logger.info(f"Creating new lecture: {lecture}")
    new_lecture = lecture_service.create(lecture)
    return ResponseModel.builder()\
        .add_message("Lecture created")\
        .add_status_code(status.HTTP_201_CREATED)\
        .add_http_status("HTTP_201_CREATED")

@app.get("/api/lectures/")
async def fetch_lectures(limit: int = 10, offset: int = 0, lecture_service: LectureService = Depends(get_lecture_service)):
    logger.info("Fetching all lectures")
    lectures = lecture_service.get_list(limit=limit, offset=offset)
    response: ResponseModel = ResponseModel.builder() \
        .add_message("Lectures fetched") \
        .add_status_code(status.HTTP_200_OK) \
        .add_http_status("HTTP_200_OK") \
        .add_data(
            {
                "lectures":  lectures
            }
        )
    return response

@app.get("/api/lectures/{lecture_id}")
async def get_lecture(lecture_id: int, lecture_service: LectureService = Depends(get_lecture_service)):
    lecture = lecture_service.get(lecture_id)
    if not lecture:
        raise HTTPException(status_code=404, detail="Lecture not found")
    return ResponseModel.builder() \
        .add_message("Lecture found") \
        .add_status_code(status.HTTP_200_OK) \
        .add_http_status("HTTP_200_OK") \
        .add_data(
            {
                "lecture":  lecture,
                "lecture_topics": lecture.topics
            }
        )

@app.delete("/api/lectures/{lecture_id}")
async def delete_lecture(lecture_id: int, lecture_service: LectureService = Depends(get_lecture_service)):
    result_ok = lecture_service.delete(lecture_id)
    if result_ok:
        return ResponseModel.builder() \
            .add_message("Lecture deleted") \
            .add_status_code(status.HTTP_200_OK) \
            .add_http_status("HTTP_200_OK")
    else:
        raise HTTPException(status_code=404, detail="Lecture not found")


# main page -- get
@app.get("/")
async def main(request: Request):
    logger.debug("Main page visited")
    return templates.TemplateResponse("index.html", {"request": request})

# upload form to load a video
@app.get("/api/lecture/upload")
async def get_loading_form(request: Request):
    logger.debug("Getting to the loading form")
    return templates.TemplateResponse("load_video_form.html", {"request": request})


# Mapping for uploading a video

@app.post("/api/lecture/upload")
async def upload_video(video: UploadFile = File(...), lecture_service: LectureService = Depends(get_lecture_service)): ## File(...) means that File is required and file from HTML form is expected
    """
    This function calls a service for processing a video --> sampling images
    :param video: uploaded file
    :param lecture_service
    :return: ResponseObject with a result
    """
    video_path = os.path.join(paths.UPLOAD_FOLDER, video.filename)
    print(f"Opening {video_path}")
    with open(video_path, "wb") as buffer:
        shutil.copyfileobj(video.file, buffer)

    image_sampler_service: ImageSamplerService = ImageSamplerService(video_path)
    slides_count = image_sampler_service.extract_slides(paths.SLIDES_OUTPUT_FOLDER)
    logger.info(f"Extracted {slides_count} slides that are stored in {paths.SLIDES_OUTPUT_FOLDER}")
    ocr_service: AbstractOCRService = TesseractOCRService()
    logger.info(f"Performing OCR...")
    text: str = ocr_service.perform_ocr()

    ## classification of the text
    # maybe experiment with other bigger models, maybe add some multilingual
    text_classification_service: AbstractTextClassificationService = ZeroShotTextClassificationService(modelid="roberta-large-mnli")
    classification_result: Dict[str, float] = text_classification_service.classify(text)

    ## text summarization
    text_summarization_service: AbstractTextSummarizationService = SimpleTextSummarizationService(modelid="t5-small")
    summary: str = text_summarization_service.make_summary(text)

    ##audio service
    logger.info(f"Generating audio embedding...")
    audio_embedding_service: EmbeddingAudioService = EmbeddingAudioService(video_path)
    audio_embedding: np.array = audio_embedding_service.create_embedding()
    # my_audio_service: AbstractAudioService = MyAudioService(video_path)
    # gender = my_audio_service.determine_gender()
    # spoken_language = my_audio_service.determine_spoken_language()

    lecture_topics = []
    for label, probability in classification_result.items():
        lecture_topic: LectureTopicBase = LectureTopicBase(
            topic_category=label,
            probability=probability
        )
        lecture_topics.append(lecture_topic)
    new_lecture: LectureBase = LectureBase(
        text=text,
        summary=summary,
        duration_seconds=random.randint(1, 100),
        lecturer="m",
        voice_language="eng",
        slides_language="eng",
        images=[],
        topics=lecture_topics,
        vdu_data= []
    )

    db_lecture_item = lecture_service.create(new_lecture)
    id = db_lecture_item.id

    ## text representation --> embedding
    text_representation_service: AbstractTextRepresentationService = SimpleTextRepresentationService(
        modelid="sentence-transformers/sentence-t5-base")
    embedding_vector: np.ndarray = text_representation_service.create_vector_representation(text)
    logger.info(f"Inserting into vector database")
    utils.postgres_vector_database.insert_into_vector_database(id, text, embedding_vector, audio_embedding)

    if slides_count > 0:
        return ResponseModel.builder() \
            .add_message("Images from a lecture created") \
            .add_developer_message(f"{slides_count} images extracted") \
            .add_status_code(status.HTTP_200_OK) \
            .add_http_status("HTTP_200_OK") \
            .add_data(
                {
                    "slides_count" : slides_count,
                    "text_length" : len(text),
                    "words_count": len(text.split()),
                    "text_beginning": text[:500], # just first 500 chars
                    "text_classification_results": lecture_topics
                }
            )
    else:
        raise HTTPException(status_code=400, detail="No images sampled. No text extracted")


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="localhost", port=8000, log_level="debug")