/*
 * Copyright (c) 2025-2026, Антон Аксенов
 * This file is part of iptvc project
 * MIT License: https://git.axenov.dev/IPTV/iptvc/src/branch/master/LICENSE
 */

package playlist

import (
	"axenov/iptv-checker/app/utils"
	"errors"
	"os"
	"regexp"
	"strings"
	"time"
)

// Group - структура для хранения информации о группе каналов
type Group struct {
	Id         string            `json:"id"`         // Хэш MD5 от названия группы
	Name       string            `json:"name"`       // Название группы (тег #EXTGRP или атрибут group-title тега #EXTINF)
	Attributes map[string]string `json:"attributes"` // Атрибуты тега #EXTGRP
}

// Channel - структура для хранения информации о канале и статусе его проверки
type Channel struct {
	Id          string            `json:"id"`          // Хэш MD5 от ссылки на канал
	Title       string            `json:"title"`       // Название канала
	URL         string            `json:"url"`         // Ссылка на канал
	GroupId     string            `json:"groupId"`     // Хэш MD5 от названия группы канала (тег #EXTGRP или атрибут group-title тега #EXTINF)
	Attributes  map[string]string `json:"attributes"`  // Атрибуты тега #EXTINF
	Status      int               `json:"status"`      // Код статуса HTTP
	IsOnline    bool              `json:"isOnline"`    // Признак доступности канала (при Status < 400)
	Error       string            `json:"error"`       // Текст ошибки (при Status >= 400)
	ContentType string            `json:"contentType"` // MIME-тип тела ответа
	Tags        []string          `json:"tags"`        // Список тегов канала
	CheckedAt   int64             `json:"checkedAt"`   // Время проверки в формате UNIX timestamp
}

// Playlist - структура для хранения информации о плейлисте
type Playlist struct {
	Code         string             `json:"code"`         // Код плейлиста (из ini-файла)
	Name         string             `json:"name"`         // Название плейлиста (из ini-файла)
	Description  string             `json:"description"`  // Описание плейлиста (из ini-файла)
	Url          string             `json:"url"`          // URL плейлиста
	Source       string             `json:"source"`       // Источник плейлиста (из ini-файла)
	Content      string             `json:"content"`      // Содержимое плейлиста (m3u, m3u8)
	IsOnline     bool               `json:"isOnline"`     // Признак доступности плейлиста
	Attributes   map[string]string  `json:"attributes"`   // Атрибуты тега #EXTM3U
	Groups       map[string]Group   `json:"groups"`       // Группы каналов (по тегам #EXTGRP и атрибутам tvg-group тегов #EXTINF)
	Channels     map[string]Channel `json:"channels"`     // Каналы
	OnlineCount  int                `json:"onlineCount"`  // Количество рабочих каналов
	OfflineCount int                `json:"offlineCount"` // Количество нерабочих каналов
	CheckedAt    int64              `json:"checkedAt"`    // Время проверки в формате UNIX timestamp
}

// MakeFromFile создаёт экземпляр плейлиста из файла
func MakeFromFile(filepath string) (Playlist, error) {
	expandedPath, err := utils.ExpandPath(filepath)
	if err != nil {
		return Playlist{}, errors.New("File read error: " + err.Error())
	}

	_, err = os.Stat(expandedPath)
	if err != nil {
		return Playlist{}, errors.New("File read error: " + err.Error())
	}

	playlist := Playlist{
		Code:        "",
		Name:        "",
		Description: "Playlist from filesystem",
		Url:         expandedPath,
		Source:      "-f",
		IsOnline:    true,
	}

	return playlist, playlist.ReadFromFs()
}

// MakeFromUrl создаёт экземпляр плейлиста из URL-адреса
func MakeFromUrl(url string) (Playlist, error) {
	playlist := Playlist{
		Code:        "",
		Name:        "",
		Description: "Remote playlist",
		Url:         url,
		Source:      "-u",
		IsOnline:    true,
	}

	return playlist, nil
}

// parseAttributes парсит атрибуты тегов #EXT*
func parseAttributes(line string) map[string]string {
	result := make(map[string]string)
	regex := regexp.MustCompile(`(?U)([a-z-]+)="(.*)"`)
	regexMatches := regex.FindAllStringSubmatch(line, -1)
	for _, match := range regexMatches {
		result[match[1]] = match[2]
	}
	return result
}

// parseTitle парсит название канала из строки тега #EXTINF
func parseTitle(line string) string {
	// сначала пытаемся по-доброму: в строке есть тег, могут быть атрибуты,
	// есть запятая-разделитель, после неё -- название канала (с запятыми или без)
	regex := regexp.MustCompile(`['"]?\s*,\s*(.+)`)
	regexMatches := regex.FindAllStringSubmatch(line, -1)
	if len(regexMatches) > 0 && len(regexMatches[0]) >= 2 {
		return strings.TrimSpace(regexMatches[0][1])
	}

	// теперь пытаемся хоть как-то: в строке есть тег, могут быть атрибуты,
	// НЕТ запятой-разделителя и название канала (с запятыми или без)
	lastQuotePos := strings.LastIndexAny(line, `,"'`)
	if lastQuotePos != -1 && lastQuotePos < len(line)-1 {
		afterLastQuote := line[lastQuotePos+1:]
		name := strings.TrimSpace(afterLastQuote)
		if name != "" {
			return name
		}
	}

	return line // ну штош
}

// Download загружает плейлист по URL-адресу
// Download скачивает плейлист по URL.
// userAgent и timeout передаются в HTTP-клиент.
func (pls *Playlist) Download(userAgent string, timeout time.Duration) error {
	content, err := utils.Fetch(pls.Url, userAgent, timeout)
	if err != nil {
		pls.Content = err.Error()
		pls.CheckedAt = time.Now().Unix()
		return err
	}

	pls.Content = string(content)
	return nil
}

// ReadFromFs читает плейлист из файла
func (pls *Playlist) ReadFromFs() error {
	content, err := os.ReadFile(pls.Url)
	if err != nil {
		pls.Content = err.Error()
		pls.CheckedAt = time.Now().Unix()
		return err
	}

	pls.Content = string(content)
	return nil
}

// Parse разбирает плейлист
func (pls *Playlist) Parse() Playlist {
	isChannel := false
	var ch Channel
	pls.Attributes = make(map[string]string)
	pls.Channels = make(map[string]Channel)
	pls.Groups = make(map[string]Group)

	content := pls.Content
	content = strings.ReplaceAll(content, "\r\n", "\n")       // replace windows line endings
	content = strings.ReplaceAll(content, "\xef\xbb\xbf", "") // remove UTF8 BOM

	for _, line := range strings.Split(content, "\n") {
		line = strings.Trim(line, "\t\r\n ")
		if line == "" || line == "#EXTM3U" {
			continue
		}

		if strings.HasPrefix(line, "#EXTM3U") {
			pls.Attributes = parseAttributes(line)
			continue
		}

		if strings.HasPrefix(line, "#EXTINF") {
			isChannel = true
			ch = Channel{Attributes: parseAttributes(line)}
			ch.Title = parseTitle(line)

			if ch.Title == "" {
				if tvgid, ok := ch.Attributes["tvg-id"]; ok {
					ch.Title = "(канал без названия, tvg-id=" + tvgid + ")"
				} else {
					ch.Title = "(канал без названия, tvg-id неизвестен)"
				}
			}

			if groupName, ok := ch.Attributes["group-title"]; ok {
				id := utils.Md5str(groupName)
				ch.GroupId = id
				pls.Groups[id] = Group{
					Id:         id,
					Name:       groupName,
					Attributes: nil,
				}
			}
			continue
		}

		if isChannel && strings.HasPrefix(line, "#EXTGRP") {
			parts := strings.Split(line, ":")
			groupName := strings.Trim(parts[1], " ")
			id := utils.Md5str(groupName)
			ch.GroupId = id
			pls.Groups[id] = Group{
				Id:         id,
				Name:       groupName,
				Attributes: nil,
			}
			continue
		}

		if isChannel && strings.HasPrefix(line, "http") {
			ch.URL = strings.Trim(line, " ")
			ch.Id = utils.Md5str(ch.URL)
			if ch.Id != "" {
				pls.Channels[ch.Id] = ch
				isChannel = false
				ch = Channel{}
			}
		}
	}

	return *pls
}
