package api

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"time"

	"MobileMkch/cache"
	"MobileMkch/models"
)

const (
	BaseURL = "https://mkch.pooziqo.xyz"
	ApiURL  = BaseURL + "/api"
)

type Client struct {
	httpClient *http.Client
	baseURL    string
	debug      bool
	authKey    string
	passcode   string
}

func NewClient() *Client {
	jar, _ := cookiejar.New(nil)
	return &Client{
		httpClient: &http.Client{
			Timeout: 30 * time.Second,
			Jar:     jar,
		},
		baseURL: BaseURL,
		debug:   false,
	}
}

func (c *Client) EnableDebug(enable bool) {
	c.debug = enable
	if c.debug {
		fmt.Printf("[DEBUG] Debug режим включен\n")
	}
}

func (c *Client) Authenticate(authKey string) error {
	resp, err := c.httpClient.Get(fmt.Sprintf("%s/key/auth/", c.baseURL))
	if err != nil {
		return fmt.Errorf("ошибка получения формы аутентификации: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("ошибка получения формы аутентификации: %d", resp.StatusCode)
	}
	
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("ошибка чтения формы аутентификации: %w", err)
	}
	
	csrfToken := extractCSRFToken(string(body))
	if csrfToken == "" {
		return fmt.Errorf("не удалось извлечь CSRF токен для аутентификации")
	}
	
	formData := url.Values{}
	formData.Set("csrfmiddlewaretoken", csrfToken)
	formData.Set("key", authKey)
	
	req, err := http.NewRequest("POST", fmt.Sprintf("%s/key/auth/", c.baseURL), bytes.NewBufferString(formData.Encode()))
	if err != nil {
		return fmt.Errorf("ошибка создания запроса аутентификации: %w", err)
	}
	
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", fmt.Sprintf("%s/key/auth/", c.baseURL))
	
	resp, err = c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("ошибка отправки аутентификации: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
		return fmt.Errorf("ошибка аутентификации: %d", resp.StatusCode)
	}
	
	c.authKey = authKey
	return nil
}

func (c *Client) LoginWithPasscode(passcode string) error {
	resp, err := c.httpClient.Get(fmt.Sprintf("%s/passcode/enter/", c.baseURL))
	if err != nil {
		return fmt.Errorf("ошибка получения формы passcode: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("ошибка получения формы passcode: %d", resp.StatusCode)
	}
	
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("ошибка чтения формы passcode: %w", err)
	}
	
	csrfToken := extractCSRFToken(string(body))
	if csrfToken == "" {
		return fmt.Errorf("не удалось извлечь CSRF токен для passcode")
	}
	
	formData := url.Values{}
	formData.Set("csrfmiddlewaretoken", csrfToken)
	formData.Set("passcode", passcode)
	
	req, err := http.NewRequest("POST", fmt.Sprintf("%s/passcode/enter/", c.baseURL), bytes.NewBufferString(formData.Encode()))
	if err != nil {
		return fmt.Errorf("ошибка создания запроса passcode: %w", err)
	}
	
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", fmt.Sprintf("%s/passcode/enter/", c.baseURL))
	
	resp, err = c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("ошибка отправки passcode: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
		return fmt.Errorf("ошибка входа с passcode: %d", resp.StatusCode)
	}
	
	c.passcode = passcode
	return nil
}

func (c *Client) makeRequest(url string) ([]byte, error) {
	if c.debug {
		fmt.Printf("[API] Запрос: %s\n", url)
	}

	resp, err := c.httpClient.Get(url)
	if err != nil {
		return nil, fmt.Errorf("ошибка запроса: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("ошибка чтения ответа: %w", err)
	}

	if c.debug {
		fmt.Printf("[API] Статус: %d, Длина ответа: %d байт\n", resp.StatusCode, len(body))
		if len(body) < 500 {
			fmt.Printf("[API] Ответ: %s\n", string(body))
		}
	}

	if resp.StatusCode != http.StatusOK {
		return nil, &models.APIError{
			Message: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(body)),
			Code:    resp.StatusCode,
		}
	}

	return body, nil
}

func (c *Client) GetBoards() ([]models.Board, error) {
	// Поздравляю, кеш
	if boards, exists := cache.GetCache().GetBoards(); exists {
		if c.debug {
			fmt.Printf("[API] Доски загружены из кэша\n")
		}
		return boards, nil
	}

	url := ApiURL + "/boards/"
	body, err := c.makeRequest(url)
	if err != nil {
		return nil, fmt.Errorf("ошибка получения досок: %w", err)
	}

	var boards []models.Board
	if err := json.Unmarshal(body, &boards); err != nil {
		if c.debug {
			fmt.Printf("[API] Ошибка парсинга JSON: %v\n", err)
			fmt.Printf("[API] Ответ был: %s\n", string(body))
		}
		return nil, fmt.Errorf("ошибка парсинга досок: %w", err)
	}

	// Всех победили
	cache.GetCache().SetBoards(boards)

	if c.debug {
		fmt.Printf("[API] Получено досок: %d\n", len(boards))
	}

	return boards, nil
}

func (c *Client) GetThreads(boardCode string) ([]models.Thread, error) {
	if threads, exists := cache.GetCache().GetThreads(boardCode); exists {
		if c.debug {
			fmt.Printf("[API] Треды загружены из кэша для /%s/\n", boardCode)
		}
		return threads, nil
	}

	url := fmt.Sprintf("%s/board/%s", ApiURL, boardCode)
	body, err := c.makeRequest(url)
	if err != nil {
		return nil, fmt.Errorf("ошибка получения тредов доски %s: %w", boardCode, err)
	}

	var threads []models.Thread
	if err := json.Unmarshal(body, &threads); err != nil {
		if c.debug {
			fmt.Printf("[API] Ошибка парсинга JSON тредов: %v\n", err)
			fmt.Printf("[API] Ответ был: %s\n", string(body))
		}
		return nil, fmt.Errorf("ошибка парсинга тредов: %w", err)
	}

	cache.GetCache().SetThreads(boardCode, threads)

	if c.debug {
		fmt.Printf("[API] Получено тредов в /%s/: %d\n", boardCode, len(threads))
	}

	return threads, nil
}

func (c *Client) GetThread(boardCode string, threadID int) (*models.ThreadDetail, error) {
	if thread, exists := cache.GetCache().GetThreadDetail(threadID); exists {
		if c.debug {
			fmt.Printf("[API] Тред %d загружен из кэша\n", threadID)
		}
		return thread, nil
	}

	url := fmt.Sprintf("%s/board/%s/thread/%d", ApiURL, boardCode, threadID)
	body, err := c.makeRequest(url)
	if err != nil {
		return nil, fmt.Errorf("ошибка получения треда %d: %w", threadID, err)
	}

	var thread models.ThreadDetail
	if err := json.Unmarshal(body, &thread); err != nil {
		if c.debug {
			fmt.Printf("[API] Ошибка парсинга JSON треда: %v\n", err)
			fmt.Printf("[API] Ответ был: %s\n", string(body))
		}
		return nil, fmt.Errorf("ошибка парсинга треда: %w", err)
	}

	cache.GetCache().SetThreadDetail(threadID, &thread)

	if c.debug {
		fmt.Printf("[API] Получен тред: ID=%d, Title=%s\n", thread.ID, thread.Title)
	}

	return &thread, nil
}

func (c *Client) GetComments(boardCode string, threadID int) ([]models.Comment, error) {
	if comments, exists := cache.GetCache().GetComments(threadID); exists {
		if c.debug {
			fmt.Printf("[API] Комментарии загружены из кэша для треда %d\n", threadID)
		}
		return comments, nil
	}

	url := fmt.Sprintf("%s/board/%s/thread/%d/comments", ApiURL, boardCode, threadID)
	body, err := c.makeRequest(url)
	if err != nil {
		return nil, fmt.Errorf("ошибка получения комментариев треда %d: %w", threadID, err)
	}

	var comments []models.Comment
	if err := json.Unmarshal(body, &comments); err != nil {
		if c.debug {
			fmt.Printf("[API] Ошибка парсинга JSON комментариев: %v\n", err)
			fmt.Printf("[API] Ответ был: %s\n", string(body))
		}
		return nil, fmt.Errorf("ошибка парсинга комментариев: %w", err)
	}

	cache.GetCache().SetComments(threadID, comments)

	if c.debug {
		fmt.Printf("[API] Получено комментариев: %d\n", len(comments))
	}

	return comments, nil
}

func (c *Client) GetFullThread(boardCode string, threadID int) (*models.ThreadDetail, []models.Comment, error) {
	threadChan := make(chan *models.ThreadDetail, 1)
	commentsChan := make(chan []models.Comment, 1)
	errorChan := make(chan error, 2)

	go func() {
		thread, err := c.GetThread(boardCode, threadID)
		if err != nil {
			errorChan <- fmt.Errorf("ошибка получения треда: %w", err)
			return
		}
		threadChan <- thread
	}()

	go func() {
		comments, err := c.GetComments(boardCode, threadID)
		if err != nil {
			errorChan <- fmt.Errorf("ошибка получения комментариев: %w", err)
			return
		}
		commentsChan <- comments
	}()

	var thread *models.ThreadDetail
	var comments []models.Comment
	var errors []error

	for i := 0; i < 2; i++ {
		select {
		case t := <-threadChan:
			thread = t
		case c := <-commentsChan:
			comments = c
		case err := <-errorChan:
			errors = append(errors, err)
		case <-time.After(30 * time.Second):
			return nil, nil, fmt.Errorf("тайм-аут получения треда")
		}
	}

	if len(errors) > 0 {
		return nil, nil, errors[0]
	}

	return thread, comments, nil
}

func (c *Client) CreateThread(boardCode, title, text, passcode string) error {
	if passcode != "" {
		if err := c.LoginWithPasscode(passcode); err != nil {
			return fmt.Errorf("ошибка входа с passcode: %w", err)
		}
	}
	
	formURL := fmt.Sprintf("%s/boards/board/%s/new", c.baseURL, boardCode)
	
	resp, err := c.httpClient.Get(formURL)
	if err != nil {
		return fmt.Errorf("ошибка получения формы: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("ошибка получения формы: %d", resp.StatusCode)
	}
	
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("ошибка чтения формы: %w", err)
	}
	
	csrfToken := extractCSRFToken(string(body))
	if csrfToken == "" {
		return fmt.Errorf("не удалось извлечь CSRF токен")
	}
	
	formData := url.Values{}
	formData.Set("csrfmiddlewaretoken", csrfToken)
	formData.Set("title", title)
	formData.Set("text", text)

	req, err := http.NewRequest("POST", formURL, bytes.NewBufferString(formData.Encode()))
	if err != nil {
		return fmt.Errorf("ошибка создания запроса: %w", err)
	}
	
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", formURL)
	
	resp, err = c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("ошибка отправки: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
		return fmt.Errorf("ошибка сервера: %d", resp.StatusCode)
	}

	cache.GetCache().Delete("threads_" + boardCode)

	return nil
}

func (c *Client) AddComment(boardCode string, threadID int, text, passcode string) error {
	if passcode != "" {
		if err := c.LoginWithPasscode(passcode); err != nil {
			return fmt.Errorf("ошибка входа с passcode: %w", err)
		}
	}
	
	formURL := fmt.Sprintf("%s/boards/board/%s/thread/%d/comment", c.baseURL, boardCode, threadID)
	
	resp, err := c.httpClient.Get(formURL)
	if err != nil {
		return fmt.Errorf("ошибка получения формы: %w", err)
	}
	defer resp.Body.Close()
	
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("ошибка получения формы: %d", resp.StatusCode)
	}
	
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("ошибка чтения формы: %w", err)
	}
	
	csrfToken := extractCSRFToken(string(body))
	if csrfToken == "" {
		return fmt.Errorf("не удалось извлечь CSRF токен")
	}
	
	formData := url.Values{}
	formData.Set("csrfmiddlewaretoken", csrfToken)
	formData.Set("text", text)

	req, err := http.NewRequest("POST", formURL, bytes.NewBufferString(formData.Encode()))
	if err != nil {
		return fmt.Errorf("ошибка создания запроса: %w", err)
	}
	
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Referer", formURL)
	
	resp, err = c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("ошибка отправки: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
		return fmt.Errorf("ошибка сервера: %d", resp.StatusCode)
	}

	cacheKey := fmt.Sprintf("comments_%d", threadID)
	cache.GetCache().Delete(cacheKey)

	return nil
}

func extractCSRFToken(html string) string {
	re := regexp.MustCompile(`name=['"]csrfmiddlewaretoken['"]\s+value=['"]([^'"]+)['"]`)
	matches := re.FindStringSubmatch(html)
	if len(matches) > 1 {
		return matches[1]
	}
	return ""
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
