import { getGraphQLClient } from '../lib/graphql-client.js'
import {
    CANCEL_SUBSET_JOB,
    CREATE_SUBSET_JOB,
    GET_SERVICE_CAPABILITIES,
    GET_SUBSET_JOB_STATUS,
    GET_SUBSET_JOBS,
} from './queries.js'
import {
    Status,
    type CollectionWithAvailableServices,
    type DataServiceInterface,
    type SubsetJobStatus,
    type SearchOptions,
    type SubsetJobs,
    type CreateSubsetJobInput,
} from './types.js'

export const HARMONY_CONFIG = {
    baseUrl: 'https://harmony.earthdata.nasa.gov',
    cmrUrl: 'https://cmr.earthdata.nasa.gov/search',
    proxyUrl:
        'https://lpo4uv7f0h.execute-api.us-east-1.amazonaws.com/default/harmony-link-proxy',
}

export const FINAL_STATUSES = new Set<Status>([
    Status.SUCCESSFUL,
    Status.FAILED,
    Status.CANCELED,
    Status.COMPLETE_WITH_ERRORS,
])

export class HarmonyDataService implements DataServiceInterface {
    async getCollectionWithAvailableServices(
        collectionEntryId: string,
        options?: SearchOptions
    ): Promise<CollectionWithAvailableServices> {
        const client = await getGraphQLClient()

        console.log(
            'Getting collection with available services for ',
            collectionEntryId
        )

        const response = await client.query<{
            getServiceCapabilities: CollectionWithAvailableServices
        }>({
            query: GET_SERVICE_CAPABILITIES,
            variables: {
                collectionEntryId,
            },
            context: {
                headers: {
                    ...(options?.bearerToken && {
                        authorization: options.bearerToken,
                    }),
                    'x-environment': options?.environment ?? 'prod',
                },
                fetchOptions: {
                    signal: options?.signal,
                },
            },
        })

        if (response.errors) {
            throw new Error(
                `Failed to create subset job: ${response.errors[0].message}`
            )
        }

        return response.data.getServiceCapabilities
    }

    async createSubsetJob(
        input: CreateSubsetJobInput,
        options?: SearchOptions
    ): Promise<SubsetJobStatus | undefined> {
        const client = await getGraphQLClient()

        console.log('creating subset job ', CREATE_SUBSET_JOB, input)

        const response = await client.mutate<{
            createSubsetJob: SubsetJobStatus
        }>({
            mutation: CREATE_SUBSET_JOB,
            variables: {
                collectionConceptId: input.collectionConceptId,
                collectionEntryId: input.collectionEntryId,
                variableConceptIds: input.variableConceptIds,
                variableEntryIds: input.variableEntryIds,
                average: input.average,
                boundingBox: input.boundingBox,
                startDate: input.startDate,
                endDate: input.endDate,
                format: input.format,
                labels: input.labels,
            },
            context: {
                headers: {
                    ...(options?.bearerToken && {
                        authorization: options.bearerToken,
                    }),
                    'x-environment': options?.environment ?? 'prod',
                },
                fetchOptions: {
                    signal: options?.signal,
                },
            },
        })

        if (response.errors) {
            throw new Error(
                `Failed to create subset job: ${response.errors[0].message}`
            )
        }

        return response.data?.createSubsetJob
    }

    async getSubsetJobs(searchOptions?: SearchOptions): Promise<SubsetJobs> {
        const client = await getGraphQLClient()

        const response = await client.query<{
            getSubsetJobs: SubsetJobs
        }>({
            query: GET_SUBSET_JOBS,
            context: {
                headers: {
                    ...(searchOptions?.bearerToken && {
                        authorization: searchOptions.bearerToken,
                    }),
                    'x-environment': searchOptions?.environment ?? 'prod',
                },
                fetchOptions: {
                    signal: searchOptions?.signal,
                },
            },
            fetchPolicy: 'network-only',
        })

        if (response.errors) {
            throw new Error(
                `Failed to fetch subset jobs: ${response.errors[0].message}`
            )
        }

        return response.data.getSubsetJobs
    }

    async getSubsetJobStatus(
        jobId: string,
        searchOptions?: SearchOptions
    ): Promise<SubsetJobStatus> {
        const client = await getGraphQLClient()

        const response = await client.query<{
            getSubsetJobStatus: SubsetJobStatus
        }>({
            query: GET_SUBSET_JOB_STATUS,
            variables: {
                jobId,
            },
            context: {
                headers: {
                    ...(searchOptions?.bearerToken && {
                        authorization: searchOptions.bearerToken,
                    }),
                    'x-environment': searchOptions?.environment ?? 'prod',
                },
                fetchOptions: {
                    signal: searchOptions?.signal,
                },
            },
            fetchPolicy: 'no-cache', //! important, we don't want to get cached results here!
        })

        if (response.errors) {
            throw new Error(
                `Failed to create subset job: ${response.errors[0].message}`
            )
        }

        return response.data.getSubsetJobStatus
    }

    async cancelSubsetJob(
        jobId: string,
        options?: SearchOptions
    ): Promise<SubsetJobStatus> {
        const client = await getGraphQLClient()

        const response = await client.query<{
            cancelSubsetJob: SubsetJobStatus
        }>({
            query: CANCEL_SUBSET_JOB,
            variables: {
                jobId,
            },
            context: {
                headers: {
                    ...(options?.bearerToken && {
                        authorization: options.bearerToken,
                    }),
                    'x-environment': options?.environment ?? 'prod',
                },
            },
            fetchPolicy: 'no-cache', //! important, we don't want to get cached results here!
        })

        if (response.errors) {
            throw new Error(
                `Failed to cancel subset job: ${response.errors[0].message}`
            )
        }

        return response.data.cancelSubsetJob
    }

    async getSubsetJobData(
        job: SubsetJobStatus,
        options?: SearchOptions
    ): Promise<string> {
        const link = job.links.find(link => link.rel === 'data')?.href

        if (!link) {
            throw new Error('No data link found for job')
        }

        const proxyUrl = `${HARMONY_CONFIG.proxyUrl}?url=${encodeURIComponent(link)}`

        console.log('fetching data from ', proxyUrl)

        const response = await fetch(proxyUrl, {
            headers: {
                ...(options?.bearerToken && {
                    Authorization: `Bearer ${options?.bearerToken}`,
                }),
            },
            signal: options?.signal,
        })

        if (!response.ok) {
            throw new Error(
                `Failed to fetch subset job link contents: ${response.statusText}`
            )
        }

        return await response.text()
    }
}
