package handlers

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"server/internal/middleware"
	"server/internal/models"

	"github.com/gofiber/fiber/v2"
	"gorm.io/gorm"
)

type githubRepoListItem struct {
	Key          string `json:"key"`
	OwnerLogin   string `json:"ownerLogin"`
	RepoName     string `json:"repoName"`
	RepoHTMLURL  string `json:"repoHtmlUrl"`
	Private      bool   `json:"private"`
	PublishMode  string `json:"publishMode,omitempty"`
	ExamRoomID   *uint  `json:"examRoomId,omitempty"`
	ExamRoomName string `json:"examRoomName,omitempty"`
	StudentLabel string `json:"studentLabel,omitempty"`
	CreatedAt    string `json:"createdAt"`
	UpdatedAt    string `json:"updatedAt"`
	FromSimpleCare bool `json:"fromSimpleCare"`
}

func githubDeleteRepo(token, owner, repo string) error {
	apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s", url.PathEscape(owner), url.PathEscape(repo))
	req, err := http.NewRequest(http.MethodDelete, apiURL, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Accept", "application/vnd.github+json")
	req.Header.Set("X-GitHub-Api-Version", "2022-11-28")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusNotFound {
		return nil
	}
	if resp.StatusCode >= 300 {
		raw, _ := io.ReadAll(resp.Body)
		msg := strings.TrimSpace(string(raw))
		var errBody struct {
			Message string `json:"message"`
		}
		_ = json.Unmarshal(raw, &errBody)
		if m := strings.TrimSpace(errBody.Message); m != "" {
			msg = m
		}
		if msg == "" {
			msg = fmt.Sprintf("HTTP %d", resp.StatusCode)
		}
		if resp.StatusCode == http.StatusForbidden {
			return fmt.Errorf("%s — token thiếu quyền delete_repo; ngắt kết nối rồi kết nối lại GitHub", msg)
		}
		return fmt.Errorf("%s", msg)
	}
	return nil
}

func githubListUserRepos(token string) ([]struct {
	Name        string    `json:"name"`
	FullName    string    `json:"full_name"`
	HTMLURL     string    `json:"html_url"`
	Private     bool      `json:"private"`
	Description string    `json:"description"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}, error) {
	var all []struct {
		Name        string    `json:"name"`
		FullName    string    `json:"full_name"`
		HTMLURL     string    `json:"html_url"`
		Private     bool      `json:"private"`
		Description string    `json:"description"`
		CreatedAt   time.Time `json:"created_at"`
		UpdatedAt   time.Time `json:"updated_at"`
	}
	for page := 1; page <= 10; page++ {
		apiURL := fmt.Sprintf("https://api.github.com/user/repos?per_page=100&page=%d&sort=updated&direction=desc", page)
		req, err := http.NewRequest(http.MethodGet, apiURL, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("Authorization", "Bearer "+token)
		req.Header.Set("Accept", "application/vnd.github+json")
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			return nil, err
		}
		var batch []struct {
			Name        string    `json:"name"`
			FullName    string    `json:"full_name"`
			HTMLURL     string    `json:"html_url"`
			Private     bool      `json:"private"`
			Description string    `json:"description"`
			CreatedAt   time.Time `json:"created_at"`
			UpdatedAt   time.Time `json:"updated_at"`
		}
		if err := json.NewDecoder(resp.Body).Decode(&batch); err != nil {
			resp.Body.Close()
			return nil, err
		}
		resp.Body.Close()
		if resp.StatusCode >= 300 {
			return nil, fmt.Errorf("GitHub API lỗi (%d)", resp.StatusCode)
		}
		all = append(all, batch...)
		if len(batch) < 100 {
			break
		}
	}
	return all, nil
}

type repoMeta struct {
	PublishMode  string
	ExamRoomID   *uint
	ExamRoomName string
	StudentLabel string
}

func repoMetaKey(owner, name string) string {
	return strings.ToLower(owner) + "/" + strings.ToLower(name)
}

func buildRepoMetaMap(db *gorm.DB, staffID uint) map[string]repoMeta {
	out := map[string]repoMeta{}
	var tracked []models.StaffGitHubRepo
	_ = db.Where("staff_id = ?", staffID).Find(&tracked).Error
	roomNames := map[uint]string{}
	for _, t := range tracked {
		key := repoMetaKey(t.OwnerLogin, t.RepoName)
		meta := repoMeta{
			PublishMode:  t.PublishMode,
			ExamRoomID:   t.ExamRoomID,
			StudentLabel: t.StudentLabel,
		}
		if t.ExamRoomID != nil {
			rid := *t.ExamRoomID
			if name, ok := roomNames[rid]; ok {
				meta.ExamRoomName = name
			} else {
				var room models.ExamRoom
				if err := db.Select("name").First(&room, rid).Error; err == nil {
					roomNames[rid] = room.Name
					meta.ExamRoomName = room.Name
				}
			}
		}
		out[key] = meta
	}
	return out
}

func recordStaffGitHubRepo(db *gorm.DB, staffID uint, owner, repoName, htmlURL, publishMode, studentLabel string, examRoomID, submissionID *uint) {
	owner = strings.TrimSpace(owner)
	repoName = strings.TrimSpace(repoName)
	if staffID == 0 || owner == "" || repoName == "" || htmlURL == "" {
		return
	}
	_ = db.Where("staff_id = ? AND owner_login = ? AND repo_name = ?", staffID, owner, repoName).
		Delete(&models.StaffGitHubRepo{}).Error
	row := models.StaffGitHubRepo{
		StaffID:      staffID,
		OwnerLogin:   owner,
		RepoName:     repoName,
		RepoHTMLURL:  htmlURL,
		ExamRoomID:   examRoomID,
		SubmissionID: submissionID,
		StudentLabel: studentLabel,
		PublishMode:  publishMode,
	}
	_ = db.Create(&row).Error
}

// GET /api/auth/github/repos — toàn bộ repo trên tài khoản GitHub đã kết nối
func GitHubListReposHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		staffID := middleware.StaffIDFromCtx(c)
		token, ghLogin, err := loadStaffGitHubToken(db, staffID)
		if err != nil {
			return c.Status(401).JSON(fiber.Map{"error": "Chưa kết nối GitHub"})
		}
		ghRepos, err := githubListUserRepos(token)
		if err != nil {
			return c.Status(502).JSON(fiber.Map{"error": "Không tải repo từ GitHub: " + err.Error()})
		}
		metaMap := buildRepoMetaMap(db, staffID)
		out := make([]githubRepoListItem, 0, len(ghRepos))
		for _, r := range ghRepos {
			parts := strings.SplitN(r.FullName, "/", 2)
			owner := ghLogin
			if len(parts) == 2 {
				owner = parts[0]
			}
			key := owner + "/" + r.Name
			item := githubRepoListItem{
				Key:         key,
				OwnerLogin:  owner,
				RepoName:    r.Name,
				RepoHTMLURL: r.HTMLURL,
				Private:     r.Private,
				CreatedAt:   r.CreatedAt.Format(time.RFC3339),
				UpdatedAt:   r.UpdatedAt.Format(time.RFC3339),
				FromSimpleCare: strings.Contains(strings.ToLower(r.Description), "simple care"),
			}
			if meta, ok := metaMap[repoMetaKey(owner, r.Name)]; ok {
				item.PublishMode = meta.PublishMode
				item.ExamRoomID = meta.ExamRoomID
				item.ExamRoomName = meta.ExamRoomName
				item.StudentLabel = meta.StudentLabel
				item.FromSimpleCare = true
			}
			out = append(out, item)
		}
		return c.JSON(fiber.Map{"data": out, "total": len(out), "githubLogin": ghLogin})
	}
}

// POST /api/auth/github/repos/bulk-delete — repos: ["owner/name", ...]
func GitHubBulkDeleteReposHandler(db *gorm.DB) fiber.Handler {
	return func(c *fiber.Ctx) error {
		staffID := middleware.StaffIDFromCtx(c)
		token, _, err := loadStaffGitHubToken(db, staffID)
		if err != nil {
			return c.Status(401).JSON(fiber.Map{"error": "Chưa kết nối GitHub"})
		}
		var authRow models.StaffGitHubAuth
		_ = db.First(&authRow, "staff_id = ?", staffID).Error
		if !githubScopeAllowsDelete(authRow.Scope) {
			return c.Status(403).JSON(fiber.Map{
				"error": "Token GitHub chưa có quyền xóa repo (delete_repo). Ngắt kết nối rồi bấm Kết nối lại GitHub.",
			})
		}
		var req struct {
			Repos []string `json:"repos"`
			IDs   []uint   `json:"ids"` // legacy
		}
		if err := c.BodyParser(&req); err != nil {
			return c.Status(400).JSON(fiber.Map{"error": "Invalid body"})
		}
		targets := make([]struct{ owner, name string }, 0)
		for _, raw := range req.Repos {
			raw = strings.TrimSpace(raw)
			if raw == "" {
				continue
			}
			parts := strings.SplitN(raw, "/", 2)
			if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
				continue
			}
			targets = append(targets, struct{ owner, name string }{owner: parts[0], name: parts[1]})
		}
		if len(targets) == 0 && len(req.IDs) > 0 {
			var rows []models.StaffGitHubRepo
			_ = db.Where("staff_id = ? AND id IN ?", staffID, req.IDs).Find(&rows).Error
			for _, row := range rows {
				targets = append(targets, struct{ owner, name string }{owner: row.OwnerLogin, name: row.RepoName})
			}
		}
		if len(targets) == 0 {
			return c.Status(400).JSON(fiber.Map{"error": "Chọn ít nhất một repo"})
		}
		deleted := 0
		var failures []string
		for _, t := range targets {
			if err := githubDeleteRepo(token, t.owner, t.name); err != nil {
				failures = append(failures, t.name+": "+err.Error())
				continue
			}
			var row models.StaffGitHubRepo
			if err := db.Where("staff_id = ? AND owner_login = ? AND repo_name = ?", staffID, t.owner, t.name).First(&row).Error; err == nil {
				_ = db.Delete(&row).Error
				clearLinkedGitURLs(db, row)
			} else {
				clearLinkedGitURLsByName(db, t.owner, t.name)
			}
			deleted++
		}
		return c.JSON(fiber.Map{
			"ok":       deleted > 0,
			"deleted":  deleted,
			"failures": failures,
			"message":  fmt.Sprintf("Đã xóa %d repo trên GitHub", deleted),
		})
	}
}

func clearLinkedGitURLs(db *gorm.DB, row models.StaffGitHubRepo) {
	url := strings.TrimSpace(row.RepoHTMLURL)
	if url == "" {
		return
	}
	if row.ExamRoomID != nil && row.PublishMode == models.GitPublishModeRoom {
		_ = db.Model(&models.ExamRoom{}).Where("id = ? AND git_repo_url = ?", *row.ExamRoomID, url).
			Updates(map[string]any{"git_repo_url": "", "git_publish_url": ""}).Error
	}
	if row.SubmissionID != nil {
		_ = db.Model(&models.ExamSubmission{}).Where("id = ? AND git_repo_url = ?", *row.SubmissionID, url).
			Updates(map[string]any{"git_repo_url": "", "git_publish_url": ""}).Error
	}
}

func clearLinkedGitURLsByName(db *gorm.DB, owner, repoName string) {
	prefix := fmt.Sprintf("https://github.com/%s/%s", owner, repoName)
	_ = db.Model(&models.ExamRoom{}).Where("git_repo_url LIKE ?", prefix+"%").
		Updates(map[string]any{"git_repo_url": "", "git_publish_url": ""}).Error
	_ = db.Model(&models.ExamSubmission{}).Where("git_repo_url LIKE ?", prefix+"%").
		Updates(map[string]any{"git_repo_url": "", "git_publish_url": ""}).Error
}
