// [DEF:GitServiceClient:Module]
/**
 * @TIER:      STANDARD
 * @SEMANTICS: git, service, api, client
 * @PURPOSE:   API client for Git operations, managing the communication between frontend and backend.
 * @LAYER:     Service
 * @RELATION:  DEPENDS_ON -> specs/011-git-integration-dashboard/contracts/api.md
 */

import { requestApi } from '../lib/api';

const API_BASE = '/git';

function buildDashboardRepoEndpoint(dashboardRef, suffix, envId = null) {
    const encodedRef = encodeURIComponent(String(dashboardRef));
    const endpoint = `${API_BASE}/repositories/${encodedRef}${suffix}`;
    if (!envId) return endpoint;
    const sep = endpoint.includes('?') ? '&' : '?';
    return `${endpoint}${sep}env_id=${encodeURIComponent(String(envId))}`;
}

// [DEF:gitService:Action]
export const gitService = {
    /**
     * [DEF:getConfigs:Function]
     * @purpose Fetches all Git server configurations.
     * @pre     User must be authenticated.
     * @post    Returns a list of Git server configurations.
     * @returns {Promise<Array>} List of configs.
     */
    async getConfigs() {
        console.log('[getConfigs][Action] Fetching Git configs');
        return requestApi(`${API_BASE}/config`);
    },

    /**
     * [DEF:createConfig:Function]
     * @purpose Creates a new Git server configuration.
     * @pre     Config object must be valid.
     * @post    New config is created and returned.
     * @param   {Object} config - Configuration details.
     * @returns {Promise<Object>} Created config.
     */
    async createConfig(config) {
        console.log('[createConfig][Action] Creating Git config');
        return requestApi(`${API_BASE}/config`, 'POST', config);
    },

    /**
     * [DEF:deleteConfig:Function]
     * @purpose Deletes an existing Git server configuration.
     * @pre     configId must exist.
     * @post    Config is deleted from the backend.
     * @param   {string} configId - ID of the config to delete.
     * @returns {Promise<Object>} Result of deletion.
     */
    async deleteConfig(configId) {
        console.log(`[deleteConfig][Action] Deleting Git config ${configId}`);
        return requestApi(`${API_BASE}/config/${configId}`, 'DELETE');
    },

    /**
     * [DEF:updateConfig:Function]
     * @purpose Updates an existing Git server configuration.
     * @pre     configId must exist and configData must be valid.
     * @post    Config is updated and returned.
     * @param   {string} configId - ID of the config to update.
     * @param   {Object} config - Updated configuration details.
     * @returns {Promise<Object>} Updated config.
     */
    async updateConfig(configId, config) {
        console.log(`[updateConfig][Action] Updating Git config ${configId}`);
        return requestApi(`${API_BASE}/config/${configId}`, 'PUT', config);
    },

    /**
     * [DEF:testConnection:Function]
     * @purpose Tests the connection to a Git server with provided credentials.
     * @pre     Config must contain valid URL and PAT.
     * @post    Returns connection status (success/failure).
     * @param   {Object} config - Configuration to test.
     * @returns {Promise<Object>} Connection test result.
     */
    async testConnection(config) {
        console.log('[testConnection][Action] Testing Git connection');
        return requestApi(`${API_BASE}/config/test`, 'POST', config);
    },

    /**
     * [DEF:listGiteaRepositories:Function]
     * @purpose Lists repositories on Gitea for a saved Git configuration.
     * @pre     configId must reference a GITEA config.
     * @post    Returns repository metadata.
     * @param   {string} configId - Git configuration ID.
     * @returns {Promise<Array>} List of Gitea repositories.
     */
    async listGiteaRepositories(configId) {
        console.log(`[listGiteaRepositories][Action] Listing Gitea repositories for config ${configId}`);
        return requestApi(`${API_BASE}/config/${configId}/gitea/repos`);
    },

    /**
     * [DEF:createGiteaRepository:Function]
     * @purpose Creates a new repository on Gitea for a saved Git configuration.
     * @pre     configId must reference a GITEA config.
     * @post    Repository is created on Gitea.
     * @param   {string} configId - Git configuration ID.
     * @param   {Object} payload - {name, private, description, auto_init, default_branch}
     * @returns {Promise<Object>} Created repository payload.
     */
    async createGiteaRepository(configId, payload) {
        console.log(`[createGiteaRepository][Action] Creating Gitea repository ${payload?.name} for config ${configId}`);
        return requestApi(`${API_BASE}/config/${configId}/gitea/repos`, 'POST', payload);
    },

    /**
     * [DEF:createRemoteRepository:Function]
     * @purpose Creates repository on remote provider selected by Git config.
     * @pre     configId exists and points to supported provider config.
     * @post    Remote repository created and normalized payload returned.
     * @param   {string} configId - Git configuration ID.
     * @param   {Object} payload - {name, private, description, auto_init, default_branch}
     * @returns {Promise<Object>} Created remote repository payload.
     */
    async createRemoteRepository(configId, payload) {
        console.log(`[createRemoteRepository][Action] Creating remote repository ${payload?.name} for config ${configId}`);
        return requestApi(`${API_BASE}/config/${configId}/repositories`, 'POST', payload);
    },

    /**
     * [DEF:deleteGiteaRepository:Function]
     * @purpose Deletes a repository on Gitea for a saved Git configuration.
     * @pre     configId must reference a GITEA config.
     * @post    Repository is deleted on Gitea.
     * @param   {string} configId - Git configuration ID.
     * @param   {string} owner - Repository owner.
     * @param   {string} repoName - Repository name.
     * @returns {Promise<Object>} Deletion result.
     */
    async deleteGiteaRepository(configId, owner, repoName) {
        console.log(`[deleteGiteaRepository][Action] Deleting Gitea repository ${owner}/${repoName} for config ${configId}`);
        return requestApi(`${API_BASE}/config/${configId}/gitea/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repoName)}`, 'DELETE');
    },

    /**
     * [DEF:initRepository:Function]
     * @purpose Initializes or clones a Git repository for a dashboard.
     * @pre     Dashboard must exist and config_id must be valid.
     * @post    Repository is initialized on the backend.
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @param   {string} configId - ID of the Git config.
     * @param   {string} remoteUrl - URL of the remote repository.
     * @returns {Promise<Object>} Initialization result.
     */
    async initRepository(dashboardRef, configId, remoteUrl, envId = null) {
        console.log(`[initRepository][Action] Initializing repo for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/init', envId), 'POST', {
            config_id: configId,
            remote_url: remoteUrl
        });
    },

    /**
     * [DEF:getRepositoryBinding:Function]
     * @purpose Fetches repository binding metadata (config/provider) for dashboard.
     * @pre     Repository should be initialized for dashboard.
     * @post    Returns provider and config details for current repository.
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @returns {Promise<Object>} Repository binding payload.
     */
    async getRepositoryBinding(dashboardRef, envId = null) {
        console.log(`[getRepositoryBinding][Action] Fetching repository binding for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '', envId));
    },

    /**
     * [DEF:getBranches:Function]
     * @purpose Retrieves the list of branches for a dashboard's repository.
     * @pre     Repository must be initialized.
     * @post    Returns a list of branches.
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @returns {Promise<Array>} List of branches.
     */
    async getBranches(dashboardRef, envId = null) {
        console.log(`[getBranches][Action] Fetching branches for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/branches', envId));
    },

    /**
     * [DEF:createBranch:Function]
     * @purpose Creates a new branch in the dashboard's repository.
     * @pre     Source branch must exist.
     * @post    New branch is created.
     * @param   {number} dashboardId - ID of the dashboard.
     * @param   {string} name - New branch name.
     * @param   {string} fromBranch - Source branch name.
     * @returns {Promise<Object>} Creation result.
     */
    async createBranch(dashboardRef, name, fromBranch, envId = null) {
        console.log(`[createBranch][Action] Creating branch ${name} for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/branches', envId), 'POST', {
            name,
            from_branch: fromBranch
        });
    },

    /**
     * [DEF:checkoutBranch:Function]
     * @purpose Switches the repository to a different branch.
     * @pre     Target branch must exist.
     * @post    Repository head is moved to the target branch.
     * @param   {number} dashboardId - ID of the dashboard.
     * @param   {string} name - Branch name to checkout.
     * @returns {Promise<Object>} Checkout result.
     */
    async checkoutBranch(dashboardRef, name, envId = null) {
        console.log(`[checkoutBranch][Action] Checking out branch ${name} for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/checkout', envId), 'POST', { name });
    },

    /**
     * [DEF:commit:Function]
     * @purpose Stages and commits changes to the repository.
     * @pre     Message must not be empty.
     * @post    Changes are committed to the current branch.
     * @param   {number} dashboardId - ID of the dashboard.
     * @param   {string} message - Commit message.
     * @param   {Array} files - Optional list of files to commit.
     * @returns {Promise<Object>} Commit result.
     */
    async commit(dashboardRef, message, files, envId = null) {
        console.log(`[commit][Action] Committing changes for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/commit', envId), 'POST', { message, files });
    },

    /**
     * [DEF:push:Function]
     * @purpose Pushes local commits to the remote repository.
     * @pre     Remote must be configured and accessible.
     * @post    Remote is updated with local commits.
     * @param   {number} dashboardId - ID of the dashboard.
     * @returns {Promise<Object>} Push result.
     */
    async push(dashboardRef, envId = null) {
        console.log(`[push][Action] Pushing changes for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/push', envId), 'POST');
    },

    /**
     * [DEF:deleteRepository:Function]
     * @purpose Deletes local repository binding and workspace for dashboard.
     * @pre     Dashboard reference must resolve on backend.
     * @post    Repository record and local folder are removed.
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @returns {Promise<Object>} Deletion result.
     */
    async deleteRepository(dashboardRef, envId = null) {
        console.log(`[deleteRepository][Action] Deleting repository for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '', envId), 'DELETE');
    },

    /**
     * [DEF:pull:Function]
     * @purpose Pulls changes from the remote repository.
     * @pre     Remote must be configured and accessible.
     * @post    Local repository is updated with remote changes.
     * @param   {number} dashboardId - ID of the dashboard.
     * @returns {Promise<Object>} Pull result.
     */
    async pull(dashboardRef, envId = null) {
        console.log(`[pull][Action] Pulling changes for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/pull', envId), 'POST');
    },

    /**
     * [DEF:getMergeStatus:Function]
     * @purpose Retrieves unfinished-merge status for repository.
     * @pre     Repository must exist.
     * @post    Returns merge status payload.
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @returns {Promise<Object>} Merge status details.
     */
    async getMergeStatus(dashboardRef, envId = null) {
        console.log(`[getMergeStatus][Action] Fetching merge status for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/merge/status', envId));
    },

    /**
     * [DEF:getMergeConflicts:Function]
     * @purpose Retrieves merge conflicts list for repository.
     * @pre     Unfinished merge should be in progress.
     * @post    Returns conflict files with mine/theirs previews.
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @returns {Promise<Array>} List of conflict files.
     */
    async getMergeConflicts(dashboardRef, envId = null) {
        console.log(`[getMergeConflicts][Action] Fetching merge conflicts for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/merge/conflicts', envId));
    },

    /**
     * [DEF:resolveMergeConflicts:Function]
     * @purpose Applies conflict resolution strategies and stages resolved files.
     * @pre     resolutions contains file_path/resolution entries.
     * @post    Conflicts are resolved and staged.
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @param   {Array} resolutions - Resolution entries.
     * @returns {Promise<Object>} Resolve result.
     */
    async resolveMergeConflicts(dashboardRef, resolutions, envId = null) {
        console.log(`[resolveMergeConflicts][Action] Resolving ${Array.isArray(resolutions) ? resolutions.length : 0} conflicts for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/merge/resolve', envId), 'POST', {
            resolutions: Array.isArray(resolutions) ? resolutions : []
        });
    },

    /**
     * [DEF:abortMerge:Function]
     * @purpose Aborts current unfinished merge.
     * @pre     Repository exists.
     * @post    Merge state is aborted or reported as absent.
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @returns {Promise<Object>} Abort operation result.
     */
    async abortMerge(dashboardRef, envId = null) {
        console.log(`[abortMerge][Action] Aborting merge for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/merge/abort', envId), 'POST');
    },

    /**
     * [DEF:continueMerge:Function]
     * @purpose Finalizes unfinished merge by creating merge commit.
     * @pre     All conflicts are resolved.
     * @post    Merge commit is created.
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @param   {string} message - Optional commit message.
     * @returns {Promise<Object>} Continue result.
     */
    async continueMerge(dashboardRef, message = '', envId = null) {
        console.log(`[continueMerge][Action] Continuing merge for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/merge/continue', envId), 'POST', {
            message: String(message || '').trim() || null
        });
    },

    /**
     * [DEF:getEnvironments:Function]
     * @purpose Retrieves available deployment environments.
     * @post    Returns a list of environments.
     * @returns {Promise<Array>} List of environments.
     */
    async getEnvironments() {
        console.log('[getEnvironments][Action] Fetching environments');
        return requestApi(`${API_BASE}/environments`);
    },

    /**
     * [DEF:deploy:Function]
     * @purpose Deploys a dashboard to a target environment.
     * @pre     Environment must be active and accessible.
     * @post    Dashboard is imported into the target Superset instance.
     * @param   {number} dashboardId - ID of the dashboard.
     * @param   {string} environmentId - ID of the target environment.
     * @returns {Promise<Object>} Deployment result.
     */
    async deploy(dashboardRef, environmentId, envId = null) {
        console.log(`[deploy][Action] Deploying dashboard ${dashboardRef} to environment ${environmentId}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/deploy', envId), 'POST', {
            environment_id: environmentId
        });
    },

    /**
     * [DEF:getHistory:Function]
     * @purpose Retrieves the commit history for a dashboard.
     * @param   {number} dashboardId - ID of the dashboard.
     * @param   {number} limit - Maximum number of commits to return.
     * @returns {Promise<Array>} List of commits.
     */
    async getHistory(dashboardRef, limit = 50, envId = null) {
        console.log(`[getHistory][Action] Fetching history for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, `/history?limit=${limit}`, envId));
    },

    /**
     * [DEF:sync:Function]
     * @purpose Synchronizes the local dashboard state with the Git repository.
     * @param   {number} dashboardId - ID of the dashboard.
     * @param   {string|null} sourceEnvId - Optional source environment ID.
     * @returns {Promise<Object>} Sync result.
     */
    async sync(dashboardRef, sourceEnvId = null, envId = null) {
        console.log(`[sync][Action] Syncing dashboard ${dashboardRef}`);
        const params = new URLSearchParams();
        if (sourceEnvId) params.append('source_env_id', String(sourceEnvId));
        if (envId) params.append('env_id', String(envId));
        const query = params.toString();
        const endpoint = `${API_BASE}/repositories/${encodeURIComponent(String(dashboardRef))}/sync${query ? `?${query}` : ''}`;
        return requestApi(endpoint, 'POST');
    },

    /**
     * [DEF:getStatus:Function]
     * @purpose Fetches the current Git status for a dashboard repository.
     * @pre     dashboardId must be a valid integer.
     * @post    Returns a status object with dirty files and branch info.
     * @param   {number} dashboardId - The ID of the dashboard.
     * @returns {Promise<Object>} Status details.
     */
    async getStatus(dashboardRef, envId = null) {
        console.log(`[getStatus][Action] Fetching status for dashboard ${dashboardRef}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/status', envId));
    },

    /**
     * [DEF:getStatusesBatch:Function]
     * @purpose Fetches Git statuses for multiple dashboards in a single request.
     * @pre     dashboardIds must be an array of dashboard IDs.
     * @post    Returns a map of dashboard_id -> status payload.
     * @param   {Array<number>} dashboardIds - Dashboard IDs.
     * @returns {Promise<Object>} Batch status response.
     */
    async getStatusesBatch(dashboardIds) {
        console.log(`[getStatusesBatch][Action] Fetching statuses for ${dashboardIds.length} dashboards`);
        return requestApi(`${API_BASE}/repositories/status/batch`, 'POST', {
            dashboard_ids: dashboardIds
        });
    },

    /**
     * [DEF:getDiff:Function]
     * @purpose Retrieves the diff for specific files or the whole repository.
     * @pre     dashboardId must be a valid integer.
     * @post    Returns the Git diff string.
     * @param   {number} dashboardId - The ID of the dashboard.
     * @param   {string|null} filePath - Optional specific file path.
     * @param   {boolean} staged - Whether to show staged changes.
     * @returns {Promise<string>} The diff content.
     */
    async getDiff(dashboardRef, filePath = null, staged = false, envId = null) {
        console.log(`[getDiff][Action] Fetching diff for dashboard ${dashboardRef} (file: ${filePath}, staged: ${staged})`);
        let endpoint = `${API_BASE}/repositories/${encodeURIComponent(String(dashboardRef))}/diff`;
        const params = new URLSearchParams();
        if (filePath) params.append('file_path', filePath);
        if (staged) params.append('staged', 'true');
        if (envId) params.append('env_id', String(envId));
        if (params.toString()) endpoint += `?${params.toString()}`;
        return requestApi(endpoint);
    },

    /**
     * [DEF:promote:Function]
     * @purpose Promotes changes between branches via MR or direct merge.
     * @pre     Dashboard repository must be initialized.
     * @post    Returns promotion metadata (MR URL or direct merge status).
     * @param   {string|number} dashboardRef - Dashboard slug or id.
     * @param   {Object} payload - {from_branch,to_branch,mode,title,description,reason,draft,remove_source_branch}
     * @param   {string|null} envId - Environment id for slug resolution.
     * @returns {Promise<Object>} Promotion result.
     */
    async promote(dashboardRef, payload, envId = null) {
        console.log(`[promote][Action] Promoting ${payload?.from_branch} -> ${payload?.to_branch} for dashboard ${dashboardRef} mode=${payload?.mode}`);
        return requestApi(buildDashboardRepoEndpoint(dashboardRef, '/promote', envId), 'POST', payload);
    }
};
// [/DEF:gitService:Action]

// [/DEF:GitServiceClient:Module]
