from __future__ import annotations

import hashlib
import json
import time
import uuid
from typing import Any

from configs.kling import ENDPOINTS
from scripts.db import execute, fetch_one


def now_ms() -> int:
    return int(time.time() * 1000)


def _json(data: dict[str, Any] | None) -> str:
    return json.dumps(data or {}, ensure_ascii=False, sort_keys=True)


def create_project(project_id: str, title: str, topic: str, script_version: str = 'v1') -> None:
    ts = now_ms()
    execute('INSERT OR REPLACE INTO projects(project_id,title,topic,script_version,created_at,updated_at) VALUES(?,?,?,?,?,?)',(project_id, title, topic, script_version, ts, ts))


def create_scene(scene: dict[str, Any]) -> None:
    ts = now_ms()
    execute('INSERT OR REPLACE INTO scenes(scene_id,project_id,ordering,narration,visual_prompt,target_duration,continuity_group,scene_type,extend_flag,status,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)',(
        scene['scene_id'], scene['project_id'], scene['ordering'], scene.get('narration'), scene.get('visual_prompt'), scene.get('target_duration'), scene.get('continuity_group'), scene.get('scene_type', 'text'), 1 if scene.get('extend_flag') else 0, scene.get('status', 'pending'), ts, ts,
    ))


def create_task(scene_id: str | None, endpoint_type: str, request_payload: dict[str, Any], external_task_id: str | None = None) -> str:
    ts = now_ms()
    task_id_internal = f'kt_{uuid.uuid4().hex[:16]}'
    endpoint_path = ENDPOINTS.get(endpoint_type)
    model_name = request_payload.get('model_name')
    execute('INSERT INTO tasks(task_id_internal,scene_id,endpoint_type,endpoint_path,model_name,external_task_id,task_status,request_payload_json,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?)',(task_id_internal, scene_id, endpoint_type, endpoint_path, model_name, external_task_id, 'created', _json(request_payload), ts, ts))
    return task_id_internal


def mark_task_submitted(task_id_internal: str, kling_task_id: str | None, request_id: str | None, response: dict[str, Any]) -> None:
    ts = now_ms()
    returned_status = ((response.get('data') or {}).get('task_status')) or 'submitted'
    finished_at = ts if returned_status in ('succeed','failed') else None
    execute('UPDATE tasks SET kling_task_id=?, request_id=?, task_status=?, response_json=?, submitted_at=?, finished_at=?, updated_at=? WHERE task_id_internal=?',(kling_task_id, request_id, returned_status, _json(response), ts, finished_at, ts, task_id_internal))


def mark_task_submission_failed(task_id_internal: str, error_message: str, category: str | None = None, status_code: int | None = None, response_body: str | None = None) -> None:
    ts = now_ms()
    payload = {'error_message': error_message, 'category': category, 'status_code': status_code, 'response_body': response_body}
    execute('UPDATE tasks SET task_status=?, task_status_msg=?, error_category=?, raw_error=?, response_json=?, finished_at=?, updated_at=? WHERE task_id_internal=?',('submit_failed', error_message[:500], category, response_body or error_message, _json(payload), ts, ts, task_id_internal))
    execute('INSERT OR IGNORE INTO task_events(task_id_internal,event_type,payload_json,dedupe_key,created_at) VALUES(?,?,?,?,?)',(task_id_internal, 'submit_failed', _json(payload), hashlib.sha256(_json(payload).encode()).hexdigest(), ts))


def update_task_from_callback(payload: dict[str, Any], auth_mode: str | None = None) -> str | None:
    kling_task_id = payload.get('task_id')
    task = None
    if kling_task_id:
        task = fetch_one('SELECT task_id_internal FROM tasks WHERE kling_task_id=?', (kling_task_id,))
    if not task:
        ext = ((payload.get('task_info') or {}).get('external_task_id'))
        if ext:
            task = fetch_one('SELECT task_id_internal FROM tasks WHERE external_task_id=? ORDER BY created_at DESC LIMIT 1', (ext,))
    if not task:
        return None
    task_id_internal = task['task_id_internal']
    ts = now_ms()
    dedupe_key = hashlib.sha256(_json(payload).encode()).hexdigest()
    existing = fetch_one('SELECT event_id FROM task_events WHERE task_id_internal=? AND event_type=? AND dedupe_key=?', (task_id_internal, 'callback', dedupe_key))
    if existing:
        return task_id_internal
    parent = (payload.get('task_info') or {}).get('parent_video') or {}
    status = payload.get('task_status', 'unknown')
    finished_at = ts if status in ('succeed','failed') else None
    execute('UPDATE tasks SET task_status=?, task_status_msg=?, callback_payload_json=?, callback_received_at=?, final_unit_deduction=?, parent_video_id=?, parent_video_url=?, parent_video_duration=?, finished_at=?, callback_auth_mode=?, updated_at=? WHERE task_id_internal=?',(
        status, payload.get('task_status_msg'), _json(payload), ts, payload.get('final_unit_deduction'), parent.get('id'), parent.get('url'), parent.get('duration'), finished_at, auth_mode, ts, task_id_internal,
    ))
    execute('INSERT OR IGNORE INTO task_events(task_id_internal,event_type,payload_json,dedupe_key,created_at) VALUES(?,?,?,?,?)',(task_id_internal, 'callback', _json(payload), dedupe_key, ts))
    return task_id_internal


def create_asset(scene_id: str | None, source_task_id: str | None, asset_type: str, original_url: str | None, watermark_url: str | None = None, local_path: str | None = None, duration: str | None = None, metadata: dict[str, Any] | None = None) -> str | None:
    if not source_task_id or not original_url:
        return None
    existing = fetch_one('SELECT asset_id FROM assets WHERE source_task_id=? AND asset_type=? AND original_url=?', (source_task_id, asset_type, original_url))
    if existing:
        return existing['asset_id']
    ts = now_ms()
    asset_id = f'asset_{uuid.uuid4().hex[:16]}'
    execute('INSERT INTO assets(asset_id,scene_id,source_task_id,asset_type,original_url,watermark_url,local_path,duration,metadata_json,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)',(asset_id, scene_id, source_task_id, asset_type, original_url, watermark_url, local_path, duration, _json(metadata), ts, ts))
    return asset_id


def update_asset_local_path(asset_id: str, local_path: str) -> None:
    ts = now_ms()
    execute('UPDATE assets SET local_path=?, downloaded_at=?, updated_at=? WHERE asset_id=?', (local_path, ts, ts, asset_id))


def list_pending_download_assets() -> list[dict[str, Any]]:
    from scripts.db import connect
    conn = connect()
    try:
        rows = conn.execute("SELECT * FROM assets WHERE original_url IS NOT NULL AND (local_path IS NULL OR local_path='') ORDER BY created_at ASC").fetchall()
        return [dict(r) for r in rows]
    finally:
        conn.close()


def get_task(task_id_internal: str) -> dict[str, Any] | None:
    return fetch_one('SELECT * FROM tasks WHERE task_id_internal=?', (task_id_internal,))
