package ccv3_test

import (
	"encoding/json"
	"net/http"

	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
	. "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
	"code.cloudfoundry.org/cli/resources"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/ghttp"
)

var _ = Describe("Relationship", func() {
	var (
		client *Client
	)

	BeforeEach(func() {
		client, _ = NewTestClient()
	})

	Describe("Relationship", func() {
		Describe("MarshalJSON", func() {
			When("the isolation segment is specified by name", func() {
				It("contains the name in the marshaled JSON", func() {
					body, err := json.Marshal(resources.Relationship{GUID: "some-iso-guid"})
					expectedJSON := `{
					"data": {
						"guid": "some-iso-guid"
					}
				}`

					Expect(err).NotTo(HaveOccurred())
					Expect(body).To(MatchJSON(expectedJSON))
				})
			})

			When("the isolation segment is the empty string", func() {
				It("contains null in the marshaled JSON", func() {
					body, err := json.Marshal(resources.Relationship{GUID: ""})
					expectedJSON := `{
					"data": null
				}`

					Expect(err).NotTo(HaveOccurred())
					Expect(body).To(MatchJSON(expectedJSON))
				})
			})
		})
	})

	Describe("UpdateSpaceIsolationSegmentRelationship", func() {
		When("the assignment is successful", func() {
			BeforeEach(func() {
				response := `{
					"data": {
						"guid": "some-isolation-segment-guid"
					}
				}`

				requestBody := map[string]map[string]string{
					"data": {"guid": "some-iso-guid"},
				}
				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPatch, "/v3/spaces/some-space-guid/relationships/isolation_segment"),
						VerifyJSONRepresenting(requestBody),
						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("returns all relationships and warnings", func() {
				relationship, warnings, err := client.UpdateSpaceIsolationSegmentRelationship("some-space-guid", "some-iso-guid")
				Expect(err).NotTo(HaveOccurred())
				Expect(warnings).To(ConsistOf("this is a warning"))
				Expect(relationship).To(Equal(resources.Relationship{
					GUID: "some-isolation-segment-guid",
				}))
			})
		})
	})

	Describe("GetOrganizationDefaultIsolationSegment", func() {
		When("getting the isolation segment is successful", func() {
			BeforeEach(func() {
				response := `{
					"data": {
						"guid": "some-isolation-segment-guid"
					}
				}`

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodGet, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"),
						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("returns the relationship and warnings", func() {
				relationship, warnings, err := client.GetOrganizationDefaultIsolationSegment("some-org-guid")
				Expect(err).NotTo(HaveOccurred())
				Expect(warnings).To(ConsistOf("this is a warning"))
				Expect(relationship).To(Equal(resources.Relationship{
					GUID: "some-isolation-segment-guid",
				}))
			})
		})

		When("getting the isolation segment fails with an error", func() {
			BeforeEach(func() {
				response := `{
					"errors": [
						{
							"detail": "Organization not found",
							"title": "CF-ResourceNotFound",
							"code": 10010
						}
					]
				}`
				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodGet, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"),
						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("returns an error and warnings", func() {
				_, warnings, err := client.GetOrganizationDefaultIsolationSegment("some-org-guid")
				Expect(err).To(MatchError(ccerror.ResourceNotFoundError{
					Message: "Organization not found",
				}))
				Expect(warnings).To(ConsistOf("this is a warning"))
			})
		})
	})

	Describe("GetSpaceIsolationSegment", func() {
		When("getting the isolation segment is successful", func() {
			BeforeEach(func() {
				response := `{
					"data": {
						"guid": "some-isolation-segment-guid"
					}
				}`

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodGet, "/v3/spaces/some-space-guid/relationships/isolation_segment"),
						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("returns the relationship and warnings", func() {
				relationship, warnings, err := client.GetSpaceIsolationSegment("some-space-guid")
				Expect(err).NotTo(HaveOccurred())
				Expect(warnings).To(ConsistOf("this is a warning"))
				Expect(relationship).To(Equal(resources.Relationship{
					GUID: "some-isolation-segment-guid",
				}))
			})
		})
	})

	Describe("UpdateOrganizationDefaultIsolationSegmentRelationship", func() {
		When("patching the default organization isolation segment with non-empty isolation segment guid", func() {
			BeforeEach(func() {
				expectedBody := `{
					"data": {
						"guid": "some-isolation-segment-guid"
					}
				}`
				responseBody := `{
					"data": {
						"guid": "some-isolation-segment-guid"
					}
				}`

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPatch, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"),
						VerifyJSON(expectedBody),
						RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("patches the organization's default isolation segment", func() {
				relationship, warnings, err := client.UpdateOrganizationDefaultIsolationSegmentRelationship("some-org-guid", "some-isolation-segment-guid")
				Expect(relationship).To(Equal(resources.Relationship{GUID: "some-isolation-segment-guid"}))
				Expect(err).ToNot(HaveOccurred())
				Expect(warnings).To(ConsistOf("this is a warning"))
			})
		})

		When("patching the default organization isolation segment with empty isolation segment guid", func() {
			BeforeEach(func() {
				expectedBody := `{
					"data": null
				}`
				responseBody := `{
					"data": null
				}`
				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPatch, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"),
						VerifyJSON(expectedBody),
						RespondWith(http.StatusOK, responseBody, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("patches the organization's default isolation segment with nil guid", func() {
				relationship, warnings, err := client.UpdateOrganizationDefaultIsolationSegmentRelationship("some-org-guid", "")
				Expect(relationship).To(BeZero())
				Expect(err).ToNot(HaveOccurred())
				Expect(warnings).To(ConsistOf("this is a warning"))
			})
		})

		When("patching the isolation segment fails with an error", func() {
			BeforeEach(func() {
				response := `{
					"errors": [
						{
							"detail": "Organization not found",
							"title": "CF-ResourceNotFound",
							"code": 10010
						}
					]
				}`

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPatch, "/v3/organizations/some-org-guid/relationships/default_isolation_segment"),
						RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("returns the empty relationship, an error and warnings", func() {
				relationship, warnings, err := client.UpdateOrganizationDefaultIsolationSegmentRelationship("some-org-guid", "some-isolation-segment-guid")
				Expect(relationship).To(BeZero())
				Expect(err).To(MatchError(ccerror.ResourceNotFoundError{
					Message: "Organization not found",
				}))
				Expect(warnings).To(ConsistOf("this is a warning"))
			})
		})
	})

	Describe("DeleteIsolationSegmentOrganization", func() {
		When("relationship exists", func() {
			BeforeEach(func() {
				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodDelete, "/v3/isolation_segments/segment-guid/relationships/organizations/org-guid"),
						RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("revoke the relationship", func() {
				warnings, err := client.DeleteIsolationSegmentOrganization("segment-guid", "org-guid")
				Expect(err).ToNot(HaveOccurred())
				Expect(warnings).To(ConsistOf("this is a warning"))

				Expect(server.ReceivedRequests()).To(HaveLen(1))
			})
		})

		When("an error occurs", func() {
			BeforeEach(func() {
				response := `{
					"errors": [
						{
							"code": 10008,
							"detail": "The request is semantically invalid: command presence",
							"title": "CF-UnprocessableEntity"
						},
						{
							"code": 10008,
							"detail": "The request is semantically invalid: command presence",
							"title": "CF-UnprocessableEntity"
						}
					]
				}`

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodDelete, "/v3/isolation_segments/segment-guid/relationships/organizations/org-guid"),
						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("returns the error and warnings", func() {
				warnings, err := client.DeleteIsolationSegmentOrganization("segment-guid", "org-guid")
				Expect(err).To(MatchError(ccerror.MultiError{
					ResponseCode: http.StatusTeapot,
					Errors: []ccerror.V3Error{
						{
							Code:   10008,
							Detail: "The request is semantically invalid: command presence",
							Title:  "CF-UnprocessableEntity",
						},
						{
							Code:   10008,
							Detail: "The request is semantically invalid: command presence",
							Title:  "CF-UnprocessableEntity",
						},
					},
				}))
				Expect(warnings).To(ConsistOf("this is a warning"))
			})
		})
	})

	Describe("SetApplicationDroplet", func() {
		Context("it sets the droplet", func() {
			BeforeEach(func() {
				response := `
{
  "data": {
    "guid": "some-droplet-guid"
  },
  "links": {
    "self": {
      "href": "https://api.example.org/v3/apps/some-app-guid/relationships/current_droplet"
    },
    "related": {
      "href": "https://api.example.org/v3/apps/some-app-guid/droplets/current"
    }
  }
}`
				requestBody := map[string]interface{}{
					"data": map[string]string{
						"guid": "some-droplet-guid",
					},
				}

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPatch, "/v3/apps/some-app-guid/relationships/current_droplet"),
						VerifyJSONRepresenting(requestBody),
						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)
			})

			It("returns warnings and no error", func() {
				relationship, warnings, err := client.SetApplicationDroplet("some-app-guid", "some-droplet-guid")
				Expect(err).ToNot(HaveOccurred())
				Expect(warnings).To(ConsistOf("this is a warning"))
				Expect(relationship.GUID).To(Equal("some-droplet-guid"))
			})
		})

		When("the CC returns an error", func() {
			BeforeEach(func() {
				response := `{
  "errors": [
    {
      "code": 10008,
      "detail": "The request is semantically invalid: command presence",
      "title": "CF-UnprocessableEntity"
    },
    {
      "code": 10010,
      "detail": "App not found",
      "title": "CF-ResourceNotFound"
    }
  ]
}`
				requestBody := map[string]interface{}{
					"data": map[string]string{
						"guid": "some-droplet-guid",
					},
				}

				server.AppendHandlers(
					CombineHandlers(
						VerifyRequest(http.MethodPatch, "/v3/apps/no-such-app-guid/relationships/current_droplet"),
						VerifyJSONRepresenting(requestBody),
						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
					),
				)

			})

			It("returns the error and all warnings", func() {
				_, warnings, err := client.SetApplicationDroplet("no-such-app-guid", "some-droplet-guid")
				Expect(err).To(MatchError(ccerror.MultiError{
					ResponseCode: http.StatusTeapot,
					Errors: []ccerror.V3Error{
						{
							Code:   10008,
							Detail: "The request is semantically invalid: command presence",
							Title:  "CF-UnprocessableEntity",
						},
						{
							Code:   10010,
							Detail: "App not found",
							Title:  "CF-ResourceNotFound",
						},
					},
				}))
				Expect(warnings).To(ConsistOf("this is a warning"))
			})
		})
	})
})
