
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
from fastapi.responses import FileResponse
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
from pathlib import Path
import mysql.connector
import uuid
import os
import time
import mimetypes

from dotenv import load_dotenv
from google import genai


# =========================================================
# ENV
# =========================================================

load_dotenv()


# =========================================================
# ROUTER
# =========================================================

router = APIRouter(
    prefix="/calls",
    tags=["Calling"]
)


# =========================================================
# DATABASE CONFIG
# =========================================================

DB_HOST = os.getenv("DB_HOST", "localhost")
DB_PORT = int(os.getenv("DB_PORT", "3306"))
DB_USER = os.getenv("DB_USER", "root")
DB_PASSWORD = os.getenv("DB_PASSWORD", "")
DB_NAME = os.getenv("DB_NAME", "kinetic_crm")


def get_db():
    return mysql.connector.connect(
        host=DB_HOST,
        port=DB_PORT,
        user=DB_USER,
        password=DB_PASSWORD,
        database=DB_NAME
    )


# =========================================================
# GEMINI CONFIG
# =========================================================

GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "").strip()

# Primary + fallback models
GEMINI_MODELS = [
    "gemini-3.7-flash",
    "gemini-2.5-flash",
    "gemini-2.0-flash",
]

# Retry settings
GEMINI_MAX_RETRIES = 3
GEMINI_RETRY_DELAY = 3


if GEMINI_API_KEY:
    gemini_client = genai.Client(
        api_key=GEMINI_API_KEY
    )
else:
    gemini_client = None

    print(
        "WARNING: GEMINI_API_KEY missing. "
        "AI conclusion disabled."
    )


# =========================================================
# RECORDING DIRECTORY
# =========================================================

BASE_DIR = Path(__file__).resolve().parent

RECORDINGS_DIR = BASE_DIR / "recordings"

RECORDINGS_DIR.mkdir(
    parents=True,
    exist_ok=True
)


# =========================================================
# INIT CALL TABLE
# =========================================================

def init_calls_table():

    conn = get_db()
    cursor = conn.cursor()

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS calls (

            id INT AUTO_INCREMENT PRIMARY KEY,

            call_id VARCHAR(100) UNIQUE NOT NULL,

            lead_id INT NOT NULL,

            employee_id VARCHAR(100) DEFAULT '',

            phone_number VARCHAR(50) NOT NULL,

            lead_name VARCHAR(255) DEFAULT '',

            employee_name VARCHAR(255) DEFAULT '',

            status VARCHAR(50) DEFAULT 'initiated',

            started_at DATETIME NULL,

            ended_at DATETIME NULL,

            duration INT DEFAULT 0,

            notes TEXT,

            recording_url TEXT,

            transcript LONGTEXT,

            conclusion LONGTEXT,

            created_at DATETIME DEFAULT CURRENT_TIMESTAMP

        )
    """)

    conn.commit()

    cursor.close()
    conn.close()


# =========================================================
# ADD NEW COLUMNS TO EXISTING TABLE
# =========================================================

def ensure_call_columns():

    conn = get_db()
    cursor = conn.cursor()

    columns = {
        "recording_url": "TEXT",
        "transcript": "LONGTEXT",
        "conclusion": "LONGTEXT",
    }

    for column, definition in columns.items():

        try:

            cursor.execute(
                f"""
                ALTER TABLE calls
                ADD COLUMN {column} {definition}
                """
            )

            conn.commit()

            print(
                f"Added calls.{column}"
            )

        except mysql.connector.Error as err:

            # 1060 = duplicate column
            if err.errno == 1060:
                pass
            else:
                print(
                    f"Column check error for {column}:",
                    err
                )

    cursor.close()
    conn.close()


init_calls_table()
ensure_call_columns()


# =========================================================
# REQUEST MODELS
# =========================================================

class StartCallRequest(BaseModel):

    lead_id: int

    employee_id: str = ""

    phone_number: str

    lead_name: str = ""

    employee_name: str = ""


class EndCallRequest(BaseModel):

    status: str = "completed"

    duration: int = 0

    notes: str = ""


class ConclusionRequest(BaseModel):

    call_id: Optional[str] = None

    lead_id: Optional[int] = None

    lead_name: str = ""

    phone_number: str = ""

    duration: int = 0

    notes: str = ""

    recording_url: str = ""


# =========================================================
# START CALL
# =========================================================

@router.post("/start")
def start_call(data: StartCallRequest):

    if not data.phone_number.strip():

        raise HTTPException(
            status_code=400,
            detail="Phone number is required."
        )

    conn = get_db()
    cursor = conn.cursor()

    call_id = str(uuid.uuid4())

    now = datetime.now()

    cursor.execute(
        """
        INSERT INTO calls
        (
            call_id,
            lead_id,
            employee_id,
            phone_number,
            lead_name,
            employee_name,
            status,
            started_at
        )
        VALUES
        (
            %s,
            %s,
            %s,
            %s,
            %s,
            %s,
            %s,
            %s
        )
        """,
        (
            call_id,
            data.lead_id,
            data.employee_id,
            data.phone_number,
            data.lead_name,
            data.employee_name,
            "initiated",
            now
        )
    )

    conn.commit()

    cursor.close()
    conn.close()

    return {
        "success": True,
        "call_id": call_id,
        "lead_id": data.lead_id,
        "phone_number": data.phone_number,
        "status": "initiated",
        "started_at": now.isoformat()
    }


# =========================================================
# END CALL
# =========================================================

@router.post("/end/{call_id}")
def end_call(
    call_id: str,
    data: EndCallRequest
):

    conn = get_db()
    cursor = conn.cursor(dictionary=True)

    cursor.execute(
        """
        SELECT id
        FROM calls
        WHERE call_id = %s
        """,
        (call_id,)
    )

    existing = cursor.fetchone()

    if not existing:

        cursor.close()
        conn.close()

        raise HTTPException(
            status_code=404,
            detail="Call not found."
        )

    now = datetime.now()

    cursor.execute(
        """
        UPDATE calls

        SET
            status = %s,
            ended_at = %s,
            duration = %s,
            notes = %s

        WHERE call_id = %s
        """,
        (
            data.status,
            now,
            data.duration,
            data.notes,
            call_id
        )
    )

    conn.commit()

    cursor.close()
    conn.close()

    return {
        "success": True,
        "call_id": call_id,
        "status": data.status,
        "duration": data.duration,
        "ended_at": now.isoformat()
    }


# =========================================================
# GEMINI PROMPT
# =========================================================

CALL_ANALYSIS_PROMPT = """
You are an AI sales call analyst for a CRM.

Listen to the complete sales call recording carefully.

Identify what the SALES PERSON and CUSTOMER/LEAD discussed.

Do NOT invent information that is not present in the recording.

Give the result in clear Hindi/Hinglish.

Return the analysis in this exact structure:

CALL SUMMARY:
- Briefly explain what happened in the call.

CUSTOMER REQUIREMENT:
- What does the customer actually need?

DISCUSSION:
- Important points discussed between salesperson and customer.

DEAL / OFFER DISCUSSED:
- What product/service/price/offer was discussed?
- If no deal or price was discussed, write "No specific deal discussed."

CUSTOMER INTEREST:
- Very Interested / Interested / Neutral / Not Interested / Cannot Determine
- Give one short reason.

OBJECTIONS:
- What problem, concern, price issue or objection did the customer raise?
- If none, write "No clear objection."

SALES PERSON ACTION:
- What did the salesperson explain, promise or offer?

NEXT FOLLOW-UP:
- What should the salesperson do next?
- Mention date/time only if it was actually discussed.

FINAL OUTCOME:
- Was the lead converted, interested, pending, rejected, callback requested, or unclear?
- Give a concise reason.

IMPORTANT:
Do not assume a deal happened just because the salesperson talked about a product.
Only mark something as agreed/deal if the conversation supports it.
"""


# =========================================================
# GEMINI ERROR HELPER
# =========================================================

def is_retryable_gemini_error(error):

    text = str(error).lower()

    retry_words = [
        "503",
        "unavailable",
        "high demand",
        "overloaded",
        "temporarily unavailable",
        "internal server error",
        "deadline exceeded",
        "429",
        "resource exhausted",
    ]

    return any(
        word in text
        for word in retry_words
    )


# =========================================================
# ANALYZE RECORDING WITH GEMINI
# =========================================================

def analyze_recording_with_gemini(
    recording_path: Path
):

    if not GEMINI_API_KEY or not gemini_client:

        return {
            "success": False,
            "transcript": "",
            "conclusion": "",
            "error": "GEMINI_API_KEY missing."
        }

    if not recording_path.exists():

        return {
            "success": False,
            "transcript": "",
            "conclusion": "",
            "error": "Recording file not found."
        }

    uploaded_file = None

    # =====================================================
    # UPLOAD
    # =====================================================

    try:

        print(
            "Uploading recording to Gemini..."
        )

        mime_type = (
            mimetypes.guess_type(
                str(recording_path)
            )[0]
            or "audio/webm"
        )

        uploaded_file = gemini_client.files.upload(
            file=str(recording_path)
        )

        print(
            "Gemini file uploaded:",
            uploaded_file.name
        )

    except Exception as error:

        print(
            "GEMINI FILE UPLOAD ERROR:",
            error
        )

        return {
            "success": False,
            "transcript": "",
            "conclusion": "",
            "error": str(error)
        }

    # =====================================================
    # MODEL FALLBACK
    # =====================================================

    for model_name in GEMINI_MODELS:

        print(
            f"Trying Gemini model: {model_name}"
        )

        for attempt in range(
            1,
            GEMINI_MAX_RETRIES + 1
        ):

            try:

                response = (
                    gemini_client
                    .models
                    .generate_content(
                        model=model_name,
                        contents=[
                            CALL_ANALYSIS_PROMPT,
                            uploaded_file,
                        ]
                    )
                )

                text = (
                    response.text
                    if response and response.text
                    else ""
                )

                if not text.strip():

                    raise Exception(
                        "Gemini returned empty response."
                    )

                print(
                    f"GEMINI SUCCESS: {model_name}"
                )

                # -----------------------------------------
                # Extract transcript if model gives one.
                # -----------------------------------------

                transcript = ""

                if "TRANSCRIPT:" in text:

                    try:
                        transcript = (
                            text
                            .split(
                                "TRANSCRIPT:",
                                1
                            )[1]
                            .split(
                                "CALL SUMMARY:",
                                1
                            )[0]
                            .strip()
                        )
                    except Exception:
                        transcript = ""

                return {
                    "success": True,
                    "model": model_name,
                    "transcript": transcript,
                    "conclusion": text.strip(),
                    "error": ""
                }

            except Exception as error:

                print(
                    f"GEMINI ERROR "
                    f"{model_name} "
                    f"attempt {attempt}:",
                    error
                )

                # -----------------------------------------
                # Retry only temporary errors
                # -----------------------------------------

                if is_retryable_gemini_error(
                    error
                ):

                    if attempt < GEMINI_MAX_RETRIES:

                        wait_time = (
                            GEMINI_RETRY_DELAY
                            * attempt
                        )

                        print(
                            f"Retrying in "
                            f"{wait_time} seconds..."
                        )

                        time.sleep(
                            wait_time
                        )

                        continue

                    print(
                        f"Model {model_name} "
                        f"failed after retries."
                    )

                    break

                # -----------------------------------------
                # Non-retryable error
                # -----------------------------------------

                print(
                    f"Non-retryable Gemini "
                    f"error on {model_name}."
                )

                break

    # =====================================================
    # ALL MODELS FAILED
    # =====================================================

    return {
        "success": False,
        "transcript": "",
        "conclusion": "",
        "error": (
            "All Gemini models failed. "
            "Please try again later."
        )
    }


# =========================================================
# SAVE AI RESULT TO DATABASE
# =========================================================

def save_ai_result(
    call_id: str,
    recording_url: str,
    transcript: str,
    conclusion: str
):

    conn = get_db()
    cursor = conn.cursor()

    cursor.execute(
        """
        UPDATE calls

        SET
            recording_url = %s,
            transcript = %s,
            conclusion = %s

        WHERE call_id = %s
        """,
        (
            recording_url,
            transcript,
            conclusion,
            call_id
        )
    )

    conn.commit()

    cursor.close()
    conn.close()


# =========================================================
# RECORDING UPLOAD
# =========================================================

@router.post("/recording")
async def upload_recording(

    file: UploadFile = File(...),

    call_id: str = Form(""),

    lead_id: str = Form(""),

    employee_id: str = Form(""),

    phone_number: str = Form(""),

    lead_name: str = Form(""),

    duration: str = Form("0"),
):

    if not file:

        raise HTTPException(
            status_code=400,
            detail="Recording file required."
        )

    if not call_id:

        raise HTTPException(
            status_code=400,
            detail="call_id is required."
        )

    # =====================================================
    # FILE NAME
    # =====================================================

    safe_call_id = "".join(
        c
        for c in call_id
        if c.isalnum() or c in "-_"
    )

    extension = (
        Path(file.filename or "")
        .suffix
        or ".webm"
    )

    recording_filename = (
        f"call_{safe_call_id}"
        f"{extension}"
    )

    recording_path = (
        RECORDINGS_DIR /
        recording_filename
    )

    # =====================================================
    # SAVE FILE
    # =====================================================

    try:

        contents = await file.read()

        if not contents:

            raise HTTPException(
                status_code=400,
                detail="Recording is empty."
            )

        with open(
            recording_path,
            "wb"
        ) as recording_file:

            recording_file.write(
                contents
            )

        print(
            "RECORDING SAVED:",
            recording_path
        )

    except HTTPException:
        raise

    except Exception as error:

        print(
            "RECORDING SAVE ERROR:",
            error
        )

        raise HTTPException(
            status_code=500,
            detail="Unable to save recording."
        )

    # =====================================================
    # RECORDING URL
    # =====================================================

    recording_url = (
        f"/calls/recording/"
        f"{safe_call_id}"
    )

    # =====================================================
    # INITIAL DB SAVE
    # =====================================================

    try:

        conn = get_db()
        cursor = conn.cursor()

        cursor.execute(
            """
            UPDATE calls

            SET
                recording_url = %s,
                duration = %s

            WHERE call_id = %s
            """,
            (
                recording_url,
                int(duration or 0),
                call_id
            )
        )

        conn.commit()

        cursor.close()
        conn.close()

    except Exception as error:

        print(
            "RECORDING DB UPDATE ERROR:",
            error
        )

    # =====================================================
    # GEMINI ANALYSIS
    # =====================================================

    ai_result = (
        analyze_recording_with_gemini(
            recording_path
        )
    )

    if ai_result["success"]:

        transcript = (
            ai_result.get(
                "transcript",
                ""
            )
        )

        conclusion = (
            ai_result.get(
                "conclusion",
                ""
            )
        )

        try:

            save_ai_result(
                call_id=call_id,
                recording_url=recording_url,
                transcript=transcript,
                conclusion=conclusion
            )

            print(
                "AI CONCLUSION SAVED TO DATABASE"
            )

        except Exception as error:

            print(
                "AI DATABASE SAVE ERROR:",
                error
            )

        return {
            "success": True,

            "call_id": call_id,

            "recording_url":
                recording_url,

            "recording_path":
                str(recording_path),

            "ai_analysis": True,

            "model":
                ai_result.get(
                    "model",
                    ""
                ),

            "transcript":
                transcript,

            "conclusion":
                conclusion,
        }

    # =====================================================
    # RECORDING SAVED BUT AI FAILED
    # =====================================================

    print(
        "Recording saved, "
        "but Gemini analysis failed:",
        ai_result.get("error")
    )

    return {
        "success": True,

        "call_id": call_id,

        "recording_url":
            recording_url,

        "recording_path":
            str(recording_path),

        "ai_analysis": False,

        "transcript": "",

        "conclusion": "",

        "ai_error":
            ai_result.get(
                "error",
                "Gemini analysis failed."
            ),
    }


# =========================================================
# SERVE RECORDING
# =========================================================

@router.get("/recording/{call_id}")
def get_recording(call_id: str):

    safe_call_id = "".join(
        c
        for c in call_id
        if c.isalnum() or c in "-_"
    )

    # Search common formats
    possible_files = list(
        RECORDINGS_DIR.glob(
            f"call_{safe_call_id}.*"
        )
    )

    if not possible_files:

        raise HTTPException(
            status_code=404,
            detail="Recording not found."
        )

    recording_path = possible_files[0]

    mime_type = (
        mimetypes.guess_type(
            str(recording_path)
        )[0]
        or "audio/webm"
    )

    return FileResponse(
        path=str(recording_path),
        media_type=mime_type,
        filename=recording_path.name
    )


# =========================================================
# GET STORED CONCLUSION
# =========================================================

@router.get("/conclusion/{call_id}")
def get_conclusion(call_id: str):

    conn = get_db()
    cursor = conn.cursor(dictionary=True)

    cursor.execute(
        """
        SELECT
            call_id,
            lead_id,
            lead_name,
            employee_id,
            employee_name,
            duration,
            recording_url,
            transcript,
            conclusion,
            notes,
            status,
            started_at,
            ended_at,
            created_at

        FROM calls

        WHERE call_id = %s

        LIMIT 1
        """,
        (call_id,)
    )

    row = cursor.fetchone()

    cursor.close()
    conn.close()

    if not row:

        raise HTTPException(
            status_code=404,
            detail="Call not found."
        )

    return {
        "success": True,
        **row
    }


# =========================================================
# GENERATE / RETURN CONCLUSION
# =========================================================

@router.post("/conclusion/{call_id}")
def generate_conclusion(
    call_id: str,
    data: ConclusionRequest
):

    conn = get_db()
    cursor = conn.cursor(dictionary=True)

    cursor.execute(
        """
        SELECT
            *
        FROM calls
        WHERE call_id = %s
        LIMIT 1
        """,
        (call_id,)
    )

    call = cursor.fetchone()

    cursor.close()
    conn.close()

    if not call:

        raise HTTPException(
            status_code=404,
            detail="Call not found."
        )

    # =====================================================
    # ALREADY GENERATED
    # =====================================================

    if call.get("conclusion"):

        return {
            "success": True,

            "call_id":
                call_id,

            "recording_url":
                call.get(
                    "recording_url",
                    ""
                ),

            "transcript":
                call.get(
                    "transcript",
                    ""
                ),

            "conclusion":
                call.get(
                    "conclusion",
                    ""
                ),

            "stored":
                True
        }

    # =====================================================
    # FIND RECORDING
    # =====================================================

    possible_files = list(
        RECORDINGS_DIR.glob(
            f"call_{call_id}.*"
        )
    )

    if not possible_files:

        return {
            "success": False,

            "call_id":
                call_id,

            "conclusion":
                "",

            "message":
                "Recording abhi available nahi hai."
        }

    recording_path = possible_files[0]

    # =====================================================
    # GENERATE
    # =====================================================

    ai_result = (
        analyze_recording_with_gemini(
            recording_path
        )
    )

    if not ai_result["success"]:

        return {
            "success": False,

            "call_id":
                call_id,

            "conclusion":
                "",

            "message":
                "Gemini analysis failed.",

            "error":
                ai_result.get(
                    "error",
                    ""
                )
        }

    transcript = (
        ai_result.get(
            "transcript",
            ""
        )
    )

    conclusion = (
        ai_result.get(
            "conclusion",
            ""
        )
    )

    recording_url = (
        call.get(
            "recording_url",
            ""
        )
        or
        f"/calls/recording/{call_id}"
    )

    # =====================================================
    # SAVE
    # =====================================================

    save_ai_result(
        call_id=call_id,

        recording_url=
            recording_url,

        transcript=
            transcript,

        conclusion=
            conclusion
    )

    print(
        "CONCLUSION GENERATED AND SAVED:",
        call_id
    )

    return {
        "success": True,

        "call_id":
            call_id,

        "recording_url":
            recording_url,

        "transcript":
            transcript,

        "conclusion":
            conclusion,

        "stored":
            True,

        "model":
            ai_result.get(
                "model",
                ""
            )
    }


# =========================================================
# CALL HISTORY
# =========================================================

@router.get("/lead/{lead_id}")
def get_lead_calls(lead_id: int):

    conn = get_db()
    cursor = conn.cursor(dictionary=True)

    cursor.execute(
        """
        SELECT
            id,
            call_id,
            lead_id,
            employee_id,
            phone_number,
            lead_name,
            employee_name,
            status,
            started_at,
            ended_at,
            duration,
            notes,
            recording_url,
            transcript,
            conclusion,
            created_at

        FROM calls

        WHERE lead_id = %s

        ORDER BY id DESC
        """,
        (lead_id,)
    )

    rows = cursor.fetchall()

    cursor.close()
    conn.close()

    return rows
