package client

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"gorm.io/gorm"
)

const baseURL = "https://debrid-link.com/api/v2/"

type Client struct {
	http         *http.Client
	db           *gorm.DB
	clientID     string
	clientSecret string
	account      *DebridAccount
}

type DebridAccount struct {
	ID           uint      `gorm:"primaryKey"`
	Host         string    `gorm:"column:host" json:"host"`
	Username     string    `gorm:"column:username" json:"username"`
	Password     string    `gorm:"column:password" json:"password"`
	IsActive     bool      `gorm:"column:is_active" json:"is_active"`
	AccessToken  string    `gorm:"column:access_token"`
	RefreshToken string    `gorm:"column:refresh_token"`
	ExpiresAt    time.Time `gorm:"column:expires_at"`
	CreatedAt    time.Time `gorm:"autoCreateTime"`
	UpdatedAt    time.Time `gorm:"autoUpdateTime"`
}

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	ExpiresIn    int64  `json:"expires_in"`
}

func NewClient(db *gorm.DB) *Client {
	return &Client{
		http:         &http.Client{Timeout: 15 * time.Second},
		db:           db,
		clientID:     "bMs32shaby43qVGVKnkRqw",
		clientSecret: "XRMJ8JNJtJZBsRP8gzBnYc6huLK2cqXay3ihKId9mt4",
	}
}


func (c *Client) SetAccount(account *DebridAccount) {
	c.account = account
}

func (c *Client) refreshAccessToken(ctx context.Context) error {
	form := url.Values{}
	form.Set("client_id", c.clientID)
	form.Set("client_secret", c.clientSecret)
	form.Set("grant_type", "refresh_token")
	form.Set("refresh_token", c.account.RefreshToken)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"oauth/token", strings.NewReader(form.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := c.http.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		b, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("refresh failed: %s", string(b))
	}

	var tokenResp TokenResponse
	if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
		return err
	}

	c.account.AccessToken = tokenResp.AccessToken
	c.account.RefreshToken = tokenResp.RefreshToken
	c.account.ExpiresAt = time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)

	return c.db.Save(c.account).Error
}
func (c *Client) ToggleActiveStatus(ctx context.Context, id uint) error {
	if c.db == nil {
		return errors.New("la connexion à la base de données est manquante")
	}

	var account DebridAccount
	if err := c.db.First(&account, id).Error; err != nil {
		return fmt.Errorf("compte introuvable : %w", err)
	}

	account.IsActive = !account.IsActive
	account.UpdatedAt = time.Now()

	if err := c.db.Save(&account).Error; err != nil {
		return fmt.Errorf("échec de la mise à jour : %w", err)
	}

	return nil
}
// ===========================
// CRUD pour DebridAccount
// ===========================

func (c *Client) CreateDebridAccount(ctx context.Context, acc *DebridAccount) error {
	return c.db.Create(acc).Error
}

func (c *Client) GetDebridAccount(ctx context.Context, id uint) (*DebridAccount, error) {
	var acc DebridAccount
	if err := c.db.First(&acc, id).Error; err != nil {
		return nil, err
	}
	return &acc, nil
}

func (c *Client) ListDebridAccounts(ctx context.Context) ([]DebridAccount, error) {
	var accounts []DebridAccount
	if err := c.db.Order("id desc").Find(&accounts).Error; err != nil {
		return nil, err
	}
	return accounts, nil
}

func (c *Client) UpdateDebridAccount(ctx context.Context, acc *DebridAccount) error {
	return c.db.Save(acc).Error
}

func (c *Client) DeleteDebridAccount(ctx context.Context, id uint) error {
	return c.db.Delete(&DebridAccount{}, id).Error
}
func (c *Client) RequestDeviceCodeWithCredentials(ctx context.Context, username, password string) (*DeviceCodeResponse, error) {

	form := url.Values{}
	log.Println("[DEBUG] Envoi device_code avec :")
	log.Println("client_id:", c.clientID)
	log.Println("username:", username)
	log.Println("password (len):", len(password))
	log.Println("scope:", form.Get("scope"))
	log.Println("URL:", "https://debrid-link.com/api/oauth/device/code")
	form.Set("client_id", c.clientID)
	form.Set("grant_type", "password") // possible variation
	form.Set("username", username)
	form.Set("password", password)
	form.Set("scope", "get.post.downloader get.post.seedbox get.account get.post.stream")

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://debrid-link.com/api/oauth/device/code", strings.NewReader(form.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("device code request failed: %s", string(body))
	}

	var result DeviceCodeResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, err
	}
	return &result, nil
}



func (c *Client) RequestDeviceCode(ctx context.Context) (*DeviceCodeResponse, error) {
	form := url.Values{}
	form.Set("client_id", c.clientID)
	form.Set("scope", "get.post.downloader get.post.seedbox get.account")

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://debrid-link.com/api/oauth/device/code", strings.NewReader(form.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("device code request failed: %s", string(body))
	}

	var result DeviceCodeResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, err
	}
	return &result, nil
}


type DeviceCodeResponse struct {
	DeviceCode      string `json:"device_code"`
	UserCode        string `json:"user_code"`
	VerificationURL string `json:"verification_url"`
	ExpiresIn       int    `json:"expires_in"`
	Interval        int    `json:"interval"`
}
func (c *Client) PasswordGrant(ctx context.Context, username, password string) (*TokenResponse, error) {
	form := url.Values{}
	form.Set("client_id", c.clientID)
	form.Set("client_secret", c.clientSecret)
	form.Set("grant_type", "password")
	form.Set("username", username)
	form.Set("password", password)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://debrid-link.com/api/oauth/token", strings.NewReader(form.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("auth error: %s", string(body))
	}

	var tokens TokenResponse
	if err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil {
		return nil, err
	}

	return &tokens, nil
}

func (c *Client) PollDeviceToken(ctx context.Context, deviceCode string, interval int) (*TokenResponse, error) {
	form := url.Values{}
	form.Set("client_id", c.clientID)
	form.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
	form.Set("code", deviceCode)

	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case <-time.After(time.Duration(interval) * time.Second):
			req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://debrid-link.com/api/oauth/token", strings.NewReader(form.Encode()))
			if err != nil {
				return nil, err
			}
			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

			resp, err := c.http.Do(req)
			if err != nil {
				return nil, err
			}
			defer resp.Body.Close()

			if resp.StatusCode == 200 {
				var tokens TokenResponse
				if err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil {
					return nil, err
				}
				return &tokens, nil
			}

			body, _ := io.ReadAll(resp.Body)
			if strings.Contains(string(body), "authorization_pending") {
				continue // Attente de validation utilisateur
			}
			return nil, fmt.Errorf("device auth failed: %s", string(body))
		}
	}
}



func (c *Client) doJSON(ctx context.Context, method, path string, params url.Values, body, out interface{}) error {
	if c.account == nil {
		return errors.New("no active Debrid account")
	}
	if time.Now().After(c.account.ExpiresAt) {
		if err := c.refreshAccessToken(ctx); err != nil {
			return err
		}
	}

	urlStr := baseURL + path
	if params != nil {
		urlStr += "?" + params.Encode()
	}

	var reqBody io.Reader
	if body != nil {
		b, err := json.Marshal(body)
		if err != nil {
			return err
		}
		reqBody = bytes.NewReader(b)

		// 👇 DEBUG : afficher le body
		log.Printf("➡️ [API %s] URL: %s\nBody: %s\n", method, urlStr, string(b))
	} else {
		log.Printf("➡️ [API %s] URL: %s (empty body)", method, urlStr)
	}

	req, err := http.NewRequestWithContext(ctx, method, urlStr, reqBody)
	if err != nil {
		return err
	}

	// 👇 DEBUG : afficher le header Authorization
	authHeader := "Bearer " + c.account.AccessToken
	log.Printf("➡️ Authorization Header: %s\n", authHeader)

	req.Header.Set("Authorization", authHeader)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.http.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		data, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(data))
	}

	if out != nil {
		return json.NewDecoder(resp.Body).Decode(out)
	}
	return nil
}


// =========================== RSS ===========================
type RSSFeed struct {
	ID        string    `json:"id" gorm:"column:id;primaryKey"`
	URL       string    `json:"url" gorm:"column:url"`
	Enabled   bool      `json:"enabled" gorm:"column:enabled"`
	CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
	UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}

type RSSItem struct {
	Title   string    `json:"title" gorm:"column:title"`
	Link    string    `json:"link" gorm:"column:link"`
	PubDate time.Time `json:"pubDate" gorm:"column:pub_date"`
}
func (c *Client) ListRSS(ctx context.Context) ([]RSSFeed, error) {
	var feeds []RSSFeed
	if err := c.doJSON(ctx, "GET", "rss", nil, nil, &feeds); err != nil {
		return nil, err
	}
	return feeds, nil
}

func (c *Client) AddRSS(ctx context.Context, url string) (*RSSFeed, error) {
	var feed RSSFeed
	body := map[string]string{"url": url}
	if err := c.doJSON(ctx, "POST", "rss", nil, body, &feed); err != nil {
		return nil, err
	}
	return &feed, nil
}

func (c *Client) TestRSS(ctx context.Context, url string) ([]RSSItem, error) {
	var items []RSSItem
	body := map[string]string{"url": url}
	if err := c.doJSON(ctx, "POST", "rss/test", nil, body, &items); err != nil {
		return nil, err
	}
	return items, nil
}

func (c *Client) DeleteRSS(ctx context.Context, id string) error {
	return c.doJSON(ctx, "DELETE", fmt.Sprintf("rss/%s", id), nil, nil, nil)
}

// =========================== Seedbox ===========================
type Torrent struct {
	ID        string    `json:"id" gorm:"column:id;primaryKey"`
	Name      string    `json:"name" gorm:"column:name"`
	Status    string    `json:"status" gorm:"column:status"`
	Progress  int       `json:"progress" gorm:"column:progress"`
	Added     time.Time `json:"added" gorm:"column:added"`
	CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
	UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}

func (c *Client) ListTorrents(ctx context.Context) ([]Torrent, error) {
	var torrents []Torrent
	if err := c.doJSON(ctx, "GET", "seedbox/torrents", nil, nil, &torrents); err != nil {
		return nil, err
	}
	return torrents, nil
}

func (c *Client) AddTorrent(ctx context.Context, link string) (*Torrent, error) {
	var t Torrent
	body := map[string]string{"link": link}
	if err := c.doJSON(ctx, "POST", "seedbox/torrents", nil, body, &t); err != nil {
		return nil, err
	}
	return &t, nil
}

func (c *Client) RemoveTorrents(ctx context.Context, ids []string) error {
	body := map[string][]string{"ids": ids}
	return c.doJSON(ctx, "DELETE", "seedbox/torrents", nil, body, nil)
}

// =========================== Downloader ===========================
type Link struct {
	ID           string    `json:"id" gorm:"primaryKey;column:id"`
	Name         string    `json:"name" gorm:"column:name"`
	URL          string    `json:"url" gorm:"column:url"`                     // Lien d'origine
	DownloadURL  string    `json:"downloadUrl" gorm:"column:download_url"`    // Lien débridé direct
	Host         string    `json:"host" gorm:"column:host"`                   // Nom de l'hébergeur
	Size         int64     `json:"size" gorm:"column:size"`                   // Taille en octets
	Chunk        int       `json:"chunk" gorm:"column:chunk"`                 // Nombre de chunks
	Expired      bool      `json:"expired" gorm:"column:expired"`             // Lien expiré ou non
	Created      int64     `json:"created" gorm:"column:created"`             // Timestamp
	CreatedAt    time.Time `gorm:"autoCreateTime"`
	UpdatedAt    time.Time `gorm:"autoUpdateTime"`
}
type StreamInfo struct {
	ID          string    `json:"id" gorm:"primaryKey;column:id"`
	StreamURL   string    `json:"streamUrl" gorm:"column:stream_url"`
	DownloadURL string    `json:"downloadUrl" gorm:"column:download_url"`
	Type        string    `json:"type" gorm:"column:type"`                   // hls ou mp4
	MimeType    string    `json:"mimetype" gorm:"column:mimetype"`          // ex: video/mp4
	Domain      string    `json:"domain" gorm:"column:domain"`

	// Champs du fichier lié (ex : nom de la vidéo)
	FileID   string `json:"-" gorm:"column:file_id"`    // lien avec le champ File.ID ci-dessous
	FileName string `json:"-" gorm:"column:file_name"`  // nom fichier
	FileSize int64  `json:"-" gorm:"column:file_size"`  // taille fichier

	CreatedAt time.Time `gorm:"autoCreateTime"`
	UpdatedAt time.Time `gorm:"autoUpdateTime"`
}



func (c *Client) ListLinks(ctx context.Context) ([]Link, error) {
	var links []Link
	if err := c.doJSON(ctx, "GET", "downloader/links", nil, nil, &links); err != nil {
		return nil, err
	}
	return links, nil
}
func (c *Client) AddLink(ctx context.Context, link string) ([]Link, error) {
	var envelope struct {
		Success bool            `json:"success"`
		Value   json.RawMessage `json:"value"`
	}

	body := map[string]string{"url": link}

	// Requête brute
	if err := c.doJSON(ctx, "POST", "downloader/add", nil, body, &envelope); err != nil {
		return nil, err
	}

	var links []Link

	switch envelope.Value[0] {
	case '{':
		var single Link
		if err := json.Unmarshal(envelope.Value, &single); err != nil {
			return nil, err
		}
		links = append(links, single)
	case '[':
		if err := json.Unmarshal(envelope.Value, &links); err != nil {
			return nil, err
		}
	default:
		return nil, errors.New("format de réponse inattendu")
	}

	return links, nil
}



func (c *Client) RemoveLinks(ctx context.Context, ids []string) error {
	body := map[string][]string{"ids": ids}
	return c.doJSON(ctx, "DELETE", "downloader/links", nil, body, nil)
}

// =========================== Files ===========================
type File struct {
	ID        string    `json:"id" gorm:"column:id;primaryKey"`
	Name      string    `json:"name" gorm:"column:name"`
	Size      int64     `json:"size" gorm:"column:size"`
	Link      string    `json:"link" gorm:"column:link"`
	ParentID  string    `json:"parent_id" gorm:"column:parent_id"`
	CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
	UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}


func (c *Client) ListFiles(ctx context.Context, parentID string) ([]File, error) {
	var files []File
	path := fmt.Sprintf("files/%s", parentID)
	if err := c.doJSON(ctx, "GET", path, nil, nil, &files); err != nil {
		return nil, err
	}
	return files, nil
}

// =========================== Stream ===========================
// func (c *Client) CreateTranscode(ctx context.Context, fileID, preset string) (string, error) {
// 	var resp struct{ TranscodeID string `json:"transcodeId"` }
// 	body := map[string]string{"fileId": fileID}
// 	if err := c.doJSON(ctx, "POST", "stream/transcode", nil, body, &resp); err != nil {
// 		return "", err
// 	}
// 	return resp.TranscodeID, nil
// }

func (c *Client) CreateTranscode(ctx context.Context, fileID string) (*StreamInfo, error) {
	body := map[string]string{"id": fileID}

	var raw struct {
		Success bool `json:"success"`
		Value   struct {
			ID          string `json:"id"`
			StreamURL   string `json:"streamUrl"`
			DownloadURL string `json:"downloadUrl"`
			Type        string `json:"type"`
			MimeType    string `json:"mimetype"`
			Domain      string `json:"domain"`
			File        struct {
				ID     string `json:"id"`
				Name   string `json:"name"`
				Size   int64  `json:"size"`
				Source string `json:"source"`
			} `json:"file"`
		} `json:"value"`
	}

	path := "stream/transcode/add"
	if err := c.doJSON(ctx, "POST", path, nil, body, &raw); err != nil {
		return nil, err
	}

	info := &StreamInfo{
		ID:          raw.Value.ID,
		StreamURL:   raw.Value.StreamURL,
		DownloadURL: raw.Value.DownloadURL,
		Type:        raw.Value.Type,
		MimeType:    raw.Value.MimeType,
		Domain:      raw.Value.Domain,
		FileID:      raw.Value.File.ID,
		FileName:    raw.Value.File.Name,
		FileSize:    raw.Value.File.Size,
	}
	return info, nil
}




