package services

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

// TelegramAttachment es un archivo que un usuario envió por Telegram y que queda
// "pendiente" hasta que el agente lo asocie a una factura o a un documento de
// proyecto (o expire).
type TelegramAttachment struct {
	Path         string
	OriginalName string
	MimeType     string
	StagedAt     time.Time
}

// adjuntoHTTPClient limita cuánto se espera al descargar un archivo de Telegram.
var adjuntoHTTPClient = &http.Client{Timeout: 60 * time.Second}

const telegramAttachmentTTL = 15 * time.Minute

var (
	telegramAttachmentsMu sync.Mutex
	telegramAttachments   = map[int64]*TelegramAttachment{}
)

// DescargarDocumentoTelegram descarga un archivo de Telegram (por file_id) usando
// la API getFile + el endpoint de descarga, y lo deja en un directorio temporal.
func DescargarDocumentoTelegram(botToken, fileID, originalName, mimeType string) (string, error) {
	getFileURL := fmt.Sprintf("https://api.telegram.org/bot%s/getFile?file_id=%s", botToken, fileID)
	resp, err := adjuntoHTTPClient.Get(getFileURL) //nolint:noctx
	if err != nil {
		return "", fmt.Errorf("no se pudo consultar el archivo en Telegram: %w", err)
	}
	defer resp.Body.Close()

	var result struct {
		OK     bool `json:"ok"`
		Result struct {
			FilePath string `json:"file_path"`
		} `json:"result"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil || !result.OK || result.Result.FilePath == "" {
		return "", fmt.Errorf("Telegram no devolvió la ruta del archivo")
	}

	downloadURL := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", botToken, result.Result.FilePath)
	fresp, err := adjuntoHTTPClient.Get(downloadURL) //nolint:noctx
	if err != nil {
		return "", fmt.Errorf("no se pudo descargar el archivo: %w", err)
	}
	defer fresp.Body.Close()

	dir := "uploads/facturas/tmp"
	if err := os.MkdirAll(dir, 0755); err != nil {
		return "", fmt.Errorf("no se pudo crear el directorio temporal: %w", err)
	}
	ext := filepath.Ext(originalName)
	if ext == "" {
		ext = filepath.Ext(result.Result.FilePath)
	}
	localPath := filepath.Join(dir, fmt.Sprintf("%d%s", time.Now().UnixNano(), ext))
	out, err := os.Create(localPath)
	if err != nil {
		return "", fmt.Errorf("no se pudo guardar el archivo: %w", err)
	}
	defer out.Close()
	if _, err := io.Copy(out, fresp.Body); err != nil {
		return "", fmt.Errorf("error escribiendo el archivo: %w", err)
	}
	return localPath, nil
}

// StageTelegramAttachment registra un archivo ya descargado como pendiente para el
// chat, a la espera de que el agente lo asocie a algo (adjuntar_factura o
// adjuntar_documento_proyecto).
func StageTelegramAttachment(chatID int64, path, originalName, mimeType string) {
	telegramAttachmentsMu.Lock()
	defer telegramAttachmentsMu.Unlock()
	telegramAttachments[chatID] = &TelegramAttachment{
		Path: path, OriginalName: originalName, MimeType: mimeType, StagedAt: time.Now(),
	}
}

// PopTelegramAttachment retorna y remueve el archivo pendiente de un chat, si
// existe y no expiró (si expiró, borra el archivo temporal del disco).
func PopTelegramAttachment(chatID int64) (*TelegramAttachment, bool) {
	telegramAttachmentsMu.Lock()
	defer telegramAttachmentsMu.Unlock()
	a, ok := telegramAttachments[chatID]
	if !ok {
		return nil, false
	}
	delete(telegramAttachments, chatID)
	if time.Since(a.StagedAt) > telegramAttachmentTTL {
		_ = os.Remove(a.Path)
		return nil, false
	}
	return a, true
}

// PeekTelegramAttachment consulta el archivo pendiente de un chat sin removerlo
// (se usa para leer su contenido y pasárselo al modelo antes de que la tool
// adjuntar_factura lo consuma de verdad).
func PeekTelegramAttachment(chatID int64) (*TelegramAttachment, bool) {
	telegramAttachmentsMu.Lock()
	defer telegramAttachmentsMu.Unlock()
	a, ok := telegramAttachments[chatID]
	if !ok || time.Since(a.StagedAt) > telegramAttachmentTTL {
		return nil, false
	}
	return a, true
}

// ResolverMimeType usa el mime_type que mandó Telegram si vino, y si no,
// adivina por la extensión del archivo (Claude solo acepta ciertos tipos).
func ResolverMimeType(mimeType, filename string) string {
	if mimeType != "" {
		return mimeType
	}
	switch strings.ToLower(filepath.Ext(filename)) {
	case ".pdf":
		return "application/pdf"
	case ".jpg", ".jpeg":
		return "image/jpeg"
	case ".png":
		return "image/png"
	case ".webp":
		return "image/webp"
	case ".gif":
		return "image/gif"
	default:
		return "application/octet-stream"
	}
}

// esMimePrevisualizablePorClaude indica si la API de Claude puede recibir ese
// tipo de archivo como contenido visible (bloque "image" o "document"). Un
// tipo fuera de esta lista (docx, xlsx, zip, etc.) mandado igual como bloque
// "image" hace que Anthropic responda 400 y tumbe la respuesta del agente.
func esMimePrevisualizablePorClaude(mime string) bool {
	switch mime {
	case "application/pdf", "image/jpeg", "image/png", "image/webp", "image/gif":
		return true
	default:
		return false
	}
}
