package meta

import (
	"bytes"
	"context"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
)

type TeamCityClient struct {
	teamCityURL string
	authToken   string
}

type Test struct {
	ID   string `xml:"id,attr"`
	Name string `xml:"name,attr"`
	Href string `xml:"href,attr"`
}

type Tests struct {
	Count int    `xml:"count,attr"`
	Test  []Test `xml:"test"`
}

type TeamCityAttachmentInfo struct {
	CurrentBuildId  int  `json:"currentBuildId"`
	PreviousBuildId *int `json:"previousBuildId"`
}

type Files struct {
	XMLName xml.Name `xml:"files"`
	Files   []File   `xml:"file"`
}

type File struct {
	Name string `xml:"name,attr"`
}

func NewTeamCityClient(teamCityURL, authToken string) *TeamCityClient {
	return &TeamCityClient{
		teamCityURL: teamCityURL,
		authToken:   authToken,
	}
}

func (client *TeamCityClient) makeRequest(ctx context.Context, endpoint string, headers map[string]string) (*http.Response, error) {
	myUrl := fmt.Sprintf("%s%s", client.teamCityURL, endpoint)
	fmt.Printf("Requesting URL: %s\n", myUrl)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, myUrl, http.NoBody)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Authorization", "Bearer "+client.authToken)
	for key, value := range headers {
		req.Header.Set(key, value)
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return resp, fmt.Errorf("request failed: %s", resp.Status)
	}

	return resp, nil
}

func (client *TeamCityClient) getArtifactChildren(ctx context.Context, buildId int, testName string) ([]string, error) {
	endpoint := fmt.Sprintf("/app/rest/builds/id:%d/artifacts/children/%s", buildId, testName)
	resp, err := client.makeRequest(ctx, endpoint, nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	var files Files
	err = xml.Unmarshal(body, &files)
	if err != nil {
		return nil, err
	}

	children := make([]string, 0, len(files.Files))
	for _, child := range files.Files {
		children = append(children, child.Name)
	}

	return children, nil
}

func (client *TeamCityClient) downloadArtifact(ctx context.Context, buildId int, filePath string) ([]byte, error) {
	endpoint := fmt.Sprintf("/app/rest/builds/id:%d/artifacts/content/%s", buildId, filePath)
	resp, err := client.makeRequest(ctx, endpoint, nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

func (client *TeamCityClient) getTestHistoryUrl(ctx context.Context, testName string) (string, error) {
	endpoint := "/app/rest/tests?locator=name:" + url.QueryEscape(testName)
	resp, err := client.makeRequest(ctx, endpoint, nil)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	var tests Tests
	err = xml.Unmarshal(body, &tests)
	if err != nil {
		return "", err
	}

	if len(tests.Test) == 0 {
		return "", fmt.Errorf("test not found: %s", testName)
	}

	return fmt.Sprintf("%s/test/%s/?currentProjectId=ijplatform", client.teamCityURL, tests.Test[0].ID), nil
}

// Build represents the root element of the XML structure
type Build struct {
	XMLName    xml.Name   `xml:"build"`
	BuildType  BuildType  `xml:"buildType"`
	Properties Properties `xml:"properties"`
}

// BuildType represents the buildType element
type BuildType struct {
	ID string `xml:"id,attr"`
}

// Properties represents the properties element
type Properties struct {
	Property []Property `xml:"property"`
}

// Property represents an individual property element
type Property struct {
	Name  string `xml:"name,attr"`
	Value string `xml:"value,attr"`
}

type BuildResponse struct {
	XMLName xml.Name `xml:"build"`
	WebURL  string   `xml:"webUrl,attr"`
}

type BuildInfo struct {
	BuildTypeId string `json:"buildTypeId"`
	Number      string `json:"number"`
	BranchName  string `json:"branchName"`
	StartDate   string `json:"startDate"`
}

type Change struct {
	Version string `json:"version"`
}

type Changes struct {
	Change []Change `json:"change"`
}

func (client *TeamCityClient) getBuildType(ctx context.Context, buildID string) (string, error) {
	res, err := client.makeRequest(ctx, "/app/rest/builds/id:"+buildID, map[string]string{"Accept": "application/json"})
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	var build BuildInfo
	if err := json.NewDecoder(res.Body).Decode(&build); err != nil {
		return "", fmt.Errorf("failed to decode changes response: %w", err)
	}

	return build.BuildTypeId, nil
}

func (client *TeamCityClient) getBuildCounter(ctx context.Context, buildID string) (string, error) {
	res, err := client.makeRequest(ctx, "/app/rest/builds/id:"+buildID, map[string]string{"Accept": "application/json"})
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	var build BuildInfo
	if err := json.NewDecoder(res.Body).Decode(&build); err != nil {
		return "", fmt.Errorf("failed to decode build response: %w", err)
	}

	return build.Number, nil
}

type CommitRevisions struct {
	FirstCommit string `json:"firstCommit"`
	LastCommit  string `json:"lastCommit"`
}

func (client *TeamCityClient) getBuildInfo(ctx context.Context, buildID string) (*BuildInfo, error) {
	res, err := client.makeRequest(ctx, "/app/rest/builds/id:"+buildID, map[string]string{"Accept": "application/json"})
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	var build BuildInfo
	if err := json.NewDecoder(res.Body).Decode(&build); err != nil {
		return nil, fmt.Errorf("failed to decode build info response: %w", err)
	}

	return &build, nil
}

func (client *TeamCityClient) getChanges(ctx context.Context, buildID string) (*CommitRevisions, error) {
	res, err := client.makeRequest(ctx, "/app/rest/changes?locator=build:(id:"+buildID+")&count=10000", map[string]string{"Accept": "application/json"})
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	var changes Changes
	if err := json.NewDecoder(res.Body).Decode(&changes); err != nil {
		return nil, fmt.Errorf("failed to decode changes response: %w", err)
	}

	if len(changes.Change) == 0 {
		return nil, fmt.Errorf("no changes found for build %s", buildID)
	}

	revisions := &CommitRevisions{
		LastCommit:  changes.Change[0].Version,
		FirstCommit: changes.Change[len(changes.Change)-1].Version,
	}

	return revisions, nil
}

func (client *TeamCityClient) startBuild(ctx context.Context, buildId string, params map[string]string) (*string, error) {
	endpoint := "/app/rest/buildQueue"

	properties := Properties{
		Property: make([]Property, 0, len(params)),
	}

	for key, value := range params {
		properties.Property = append(properties.Property, Property{
			Name:  key,
			Value: value,
		})
	}

	build := Build{
		BuildType:  BuildType{ID: buildId},
		Properties: properties,
	}

	myUrl := fmt.Sprintf("%s%s", client.teamCityURL, endpoint)
	fmt.Printf("Requesting URL: %s\n", myUrl)

	buildXml, err := xml.Marshal(build)
	if err != nil {
		return nil, fmt.Errorf("error marshalling build %v: %w", build, err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, myUrl, bytes.NewBuffer(buildXml))
	if err != nil {
		return nil, fmt.Errorf("error creating request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+client.authToken)
	req.Header.Set("Content-Type", "application/xml")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("error sending request: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("request failed: %s", resp.Status)
	}

	body := resp.Body
	all, err := io.ReadAll(body)
	defer body.Close()
	if err != nil {
		return nil, fmt.Errorf("error reading body: %w", err)
	}

	var buildResponse BuildResponse
	err = xml.Unmarshal(all, &buildResponse)
	if err != nil {
		return nil, fmt.Errorf("error unmarshaling XML: %w", err)
	}
	return &buildResponse.WebURL, nil
}
