package feed

import (
	"fmt"
	"log/slog"
	"net/http"
	"sync"
	"time"
)

type githubReleaseLatestResponseJson struct {
	TagName     string `json:"tag_name"`
	PublishedAt string `json:"published_at"`
	HtmlUrl     string `json:"html_url"`
	Reactions   struct {
		Downvotes int `json:"-1"`
	} `json:"reactions"`
}

func parseGithubTime(t string) time.Time {
	parsedTime, err := time.Parse("2006-01-02T15:04:05Z", t)

	if err != nil {
		return time.Now()
	}

	return parsedTime
}

func FetchLatestReleasesFromGithub(repositories []string, token string) (AppReleases, error) {
	appReleases := make(AppReleases, 0, len(repositories))

	if len(repositories) == 0 {
		return appReleases, nil
	}

	requests := make([]*http.Request, len(repositories))

	for i, repository := range repositories {
		request, _ := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repository), nil)

		if token != "" {
			request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
		}

		requests[i] = request
	}

	task := decodeJsonFromRequestTask[githubReleaseLatestResponseJson](defaultClient)
	job := newJob(task, requests).withWorkers(15)
	responses, errs, err := workerPoolDo(job)

	if err != nil {
		return nil, err
	}

	var failed int

	for i := range responses {
		if errs[i] != nil {
			failed++
			slog.Error("Failed to fetch or parse github release", "error", errs[i], "url", requests[i].URL)
			continue
		}

		liveRelease := &responses[i]

		if liveRelease == nil {
			slog.Error("No live release found", "repository", repositories[i], "url", requests[i].URL)
			continue
		}

		version := liveRelease.TagName

		if version[0] != 'v' {
			version = "v" + version
		}

		appReleases = append(appReleases, AppRelease{
			Name:         repositories[i],
			Version:      version,
			NotesUrl:     liveRelease.HtmlUrl,
			TimeReleased: parseGithubTime(liveRelease.PublishedAt),
			Downvotes:    liveRelease.Reactions.Downvotes,
		})
	}

	if len(appReleases) == 0 {
		return nil, ErrNoContent
	}

	appReleases.SortByNewest()

	if failed > 0 {
		return appReleases, fmt.Errorf("%w: could not get %d releases", ErrPartialContent, failed)
	}

	return appReleases, nil
}

type GithubTicket struct {
	Number    int
	CreatedAt time.Time
	Title     string
}

type RepositoryDetails struct {
	Name             string
	Stars            int
	Forks            int
	OpenPullRequests int
	PullRequests     []GithubTicket
	OpenIssues       int
	Issues           []GithubTicket
}

type githubRepositoryDetailsResponseJson struct {
	Name  string `json:"full_name"`
	Stars int    `json:"stargazers_count"`
	Forks int    `json:"forks_count"`
}

type githubTicketResponseJson struct {
	Count   int `json:"total_count"`
	Tickets []struct {
		Number    int    `json:"number"`
		CreatedAt string `json:"created_at"`
		Title     string `json:"title"`
	} `json:"items"`
}

func FetchRepositoryDetailsFromGithub(repository string, token string, maxPRs int, maxIssues int) (RepositoryDetails, error) {
	repositoryRequest, err := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/repos/%s", repository), nil)

	if err != nil {
		return RepositoryDetails{}, fmt.Errorf("%w: could not create request with repository: %v", ErrNoContent, err)
	}

	PRsRequest, _ := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/search/issues?q=is:pr+is:open+repo:%s&per_page=%d", repository, maxPRs), nil)
	issuesRequest, _ := http.NewRequest("GET", fmt.Sprintf("https://api.github.com/search/issues?q=is:issue+is:open+repo:%s&per_page=%d", repository, maxIssues), nil)

	if token != "" {
		token = fmt.Sprintf("Bearer %s", token)
		repositoryRequest.Header.Add("Authorization", token)
		PRsRequest.Header.Add("Authorization", token)
		issuesRequest.Header.Add("Authorization", token)
	}

	var detailsResponse githubRepositoryDetailsResponseJson
	var detailsErr error
	var PRsResponse githubTicketResponseJson
	var PRsErr error
	var issuesResponse githubTicketResponseJson
	var issuesErr error
	var wg sync.WaitGroup

	wg.Add(1)
	go (func() {
		defer wg.Done()
		detailsResponse, detailsErr = decodeJsonFromRequest[githubRepositoryDetailsResponseJson](defaultClient, repositoryRequest)
	})()

	if maxPRs > 0 {
		wg.Add(1)
		go (func() {
			defer wg.Done()
			PRsResponse, PRsErr = decodeJsonFromRequest[githubTicketResponseJson](defaultClient, PRsRequest)
		})()
	}

	if maxIssues > 0 {
		wg.Add(1)
		go (func() {
			defer wg.Done()
			issuesResponse, issuesErr = decodeJsonFromRequest[githubTicketResponseJson](defaultClient, issuesRequest)
		})()
	}

	wg.Wait()

	if detailsErr != nil {
		return RepositoryDetails{}, fmt.Errorf("%w: could not get repository details: %s", ErrNoContent, detailsErr)
	}

	details := RepositoryDetails{
		Name:         detailsResponse.Name,
		Stars:        detailsResponse.Stars,
		Forks:        detailsResponse.Forks,
		PullRequests: make([]GithubTicket, 0, len(PRsResponse.Tickets)),
		Issues:       make([]GithubTicket, 0, len(issuesResponse.Tickets)),
	}

	err = nil

	if maxPRs > 0 {
		if PRsErr != nil {
			err = fmt.Errorf("%w: could not get PRs: %s", ErrPartialContent, PRsErr)
		} else {
			details.OpenPullRequests = PRsResponse.Count

			for i := range PRsResponse.Tickets {
				details.PullRequests = append(details.PullRequests, GithubTicket{
					Number:    PRsResponse.Tickets[i].Number,
					CreatedAt: parseGithubTime(PRsResponse.Tickets[i].CreatedAt),
					Title:     PRsResponse.Tickets[i].Title,
				})
			}
		}
	}

	if maxIssues > 0 {
		if issuesErr != nil {
			// TODO: fix, overwriting the previous error
			err = fmt.Errorf("%w: could not get issues: %s", ErrPartialContent, issuesErr)
		} else {
			details.OpenIssues = issuesResponse.Count

			for i := range issuesResponse.Tickets {
				details.Issues = append(details.Issues, GithubTicket{
					Number:    issuesResponse.Tickets[i].Number,
					CreatedAt: parseGithubTime(issuesResponse.Tickets[i].CreatedAt),
					Title:     issuesResponse.Tickets[i].Title,
				})
			}
		}
	}

	return details, err
}
