import { pipeline } from 'node:stream/promises';
import { Readable } from 'node:stream';
import { Response } from 'express';
import { canAccessTrip, db } from "../../db/database";
import { safeFetch, SsrfBlockedError } from '../../utils/ssrfGuard';
import { decrypt_api_key } from '../apiKeyCrypto';

// helpers for handling return types

type ServiceError = { success: false; error: { message: string; status: number } };
export type ServiceResult<T> = { success: true; data: T } | ServiceError;


export function fail(error: string, status: number): ServiceError {
    return { success: false, error: { message: error, status } };
}


export function success<T>(data: T): ServiceResult<T> {
    return { success: true, data: data };
}


export function mapDbError(error: Error, fallbackMessage: string): ServiceError {
    if (error && /unique|constraint/i.test(error.message)) {
        return fail('Resource already exists', 409);
    }
    return fail(error.message, 500);
}


export function handleServiceResult<T>(res: Response, result: ServiceResult<T>): void {
    if ('error' in result) {
        res.status(result.error.status).json({ error: result.error.message });
    }
    else {
        res.json(result.data);
    }
}

// ----------------------------------------------
// types used across memories services
export type Selection = {
    provider: string;
    asset_ids: string[];
    passphrase?: string;
};

export type StatusResult = {
    connected: true;
    user: { name: string }
} | {
    connected: false;
    error: string
};

export type SyncAlbumResult = {
    added: number;
    total: number
};


export type AlbumsList = {
    albums: Array<{ id: string; albumName: string; assetCount: number; passphrase?: string }>
};

export type Asset = {
    id: string;
    takenAt: string;
};

export type AssetsList = {
    assets: Asset[],
    total: number,
    hasMore: boolean
};


export type AssetInfo = {
    id: string;
    takenAt: string | null;
    city: string | null;
    country: string | null;
    state?: string | null;
    camera?: string | null;
    lens?: string | null;
    focalLength?: string | number | null;
    aperture?: string | number | null;
    shutter?: string | number | null;
    iso?: string | number | null;
    lat?: number | null;
    lng?: number | null;
    orientation?: number | null;
    description?: string | null;
    width?: number | null;
    height?: number | null;
    fileSize?: number | null;
    fileName?: string | null;
}


//for loading routes to settings page, and validating which services user has connected
type PhotoProviderConfig = {
    settings_get: string;
    settings_put: string;
    status_get: string;
    test_post: string;
};


export function getPhotoProviderConfig(providerId: string): PhotoProviderConfig {
    const prefix = `/integrations/memories/${providerId}`;
    return {
        settings_get: `${prefix}/settings`,
        settings_put: `${prefix}/settings`,
        status_get: `${prefix}/status`,
        test_post: `${prefix}/test`,
    };
}

//-----------------------------------------------
//access check helper

export function canAccessUserPhoto(requestingUserId: number, ownerUserId: number, tripId: string, assetId: string, provider: string): boolean {
    if (requestingUserId === ownerUserId) {
        return true;
    }

    // Journey photos use tripId=0 — check journey_photos + journey_contributors
    if (tripId === '0') {
        const journeyPhoto = db.prepare(`
            SELECT gp.journey_id
            FROM journey_photos gp
            JOIN trek_photos tkp ON tkp.id = gp.photo_id
            WHERE tkp.asset_id = ?
              AND tkp.provider = ?
              AND tkp.owner_id = ?
            LIMIT 1
        `).get(assetId, provider, ownerUserId) as { journey_id: number } | undefined;
        if (!journeyPhoto) return false;

        const access = db.prepare(`
            SELECT 1 FROM journeys WHERE id = ? AND user_id = ?
            UNION ALL
            SELECT 1 FROM journey_contributors WHERE journey_id = ? AND user_id = ?
            LIMIT 1
        `).get(journeyPhoto.journey_id, requestingUserId, journeyPhoto.journey_id, requestingUserId);
        return !!access;
    }

    // Regular trip photos — join through trek_photos
    const sharedAsset = db.prepare(`
    SELECT 1
    FROM trip_photos tp
    JOIN trek_photos tkp ON tkp.id = tp.photo_id
    WHERE tp.user_id = ?
      AND tkp.asset_id = ?
      AND tkp.provider = ?
      AND tp.trip_id = ?
      AND tp.shared = 1
    LIMIT 1
    `).get(ownerUserId, assetId, provider, tripId);

    if (!sharedAsset) {
        return false;
    }
    return !!canAccessTrip(tripId, requestingUserId);
}


// ── Unified photo access check (trek_photos based) ──────────────────────

export function canAccessTrekPhoto(requestingUserId: number, trekPhotoId: number): boolean {
    const photo = db.prepare('SELECT * FROM trek_photos WHERE id = ?').get(trekPhotoId) as { id: number; provider: string; owner_id: number | null } | undefined;
    if (!photo) return false;

    // Owner always has access
    if (photo.owner_id === requestingUserId) return true;

    // Check trip_photos — is this photo shared in a trip the user has access to?
    const tripAccess = db.prepare(`
        SELECT 1 FROM trip_photos tp
        WHERE tp.photo_id = ?
          AND tp.shared = 1
          AND EXISTS (
            SELECT 1 FROM trip_members tm WHERE tm.trip_id = tp.trip_id AND tm.user_id = ?
            UNION ALL
            SELECT 1 FROM trips t WHERE t.id = tp.trip_id AND t.user_id = ?
          )
        LIMIT 1
    `).get(trekPhotoId, requestingUserId, requestingUserId);
    if (tripAccess) return true;

    // Check journey_photos — is this photo in a journey the user can access?
    const journeyAccess = db.prepare(`
        SELECT 1 FROM journey_photos gp
        WHERE gp.photo_id = ?
          AND EXISTS (
            SELECT 1 FROM journeys j WHERE j.id = gp.journey_id AND j.user_id = ?
            UNION ALL
            SELECT 1 FROM journey_contributors jc WHERE jc.journey_id = gp.journey_id AND jc.user_id = ?
          )
        LIMIT 1
    `).get(trekPhotoId, requestingUserId, requestingUserId);
    if (journeyAccess) return true;

    // Local photos without owner (uploaded files) — check if user has journey access
    if (photo.provider === 'local' && !photo.owner_id) {
        return !!journeyAccess;
    }

    return false;
}


// ----------------------------------------------
//helpers for album link syncing

export function getAlbumIdFromLink(tripId: string, linkId: string, userId: number): ServiceResult<string> {
    const access = canAccessTrip(tripId, userId);
    if (!access) return fail('Trip not found or access denied', 404);

    try {
        const row = db.prepare('SELECT album_id FROM trip_album_links WHERE id = ? AND trip_id = ? AND user_id = ?')
            .get(linkId, tripId, userId) as { album_id: string } | null;

        return row ? success(row.album_id) : fail('Album link not found', 404);
    } catch {
        return fail('Failed to retrieve album link', 500);
    }
}

export function getAlbumLinkForSync(tripId: string, linkId: string, userId: number): ServiceResult<{ albumId: string; passphrase?: string }> {
    const access = canAccessTrip(tripId, userId);
    if (!access) return fail('Trip not found or access denied', 404);

    try {
        const row = db.prepare('SELECT album_id, passphrase FROM trip_album_links WHERE id = ? AND trip_id = ? AND user_id = ?')
            .get(linkId, tripId, userId) as { album_id: string; passphrase: string | null } | null;

        if (!row) return fail('Album link not found', 404);

        const decrypted = row.passphrase ? decrypt_api_key(row.passphrase) ?? undefined : undefined;
        return success({ albumId: row.album_id, passphrase: decrypted || undefined });
    } catch {
        return fail('Failed to retrieve album link', 500);
    }
}

export function updateSyncTimeForAlbumLink(linkId: string): void {
    db.prepare('UPDATE trip_album_links SET last_synced_at = CURRENT_TIMESTAMP WHERE id = ?').run(linkId);
}

export async function pipeAsset(url: string, response: Response, headers?: Record<string, string>, signal?: AbortSignal, defaultCacheControl?: string): Promise<void> {
    try {
        const resp = await safeFetch(url, { headers, signal: signal as any });

        response.status(resp.status);
        if (resp.headers.get('content-type')) response.set('Content-Type', resp.headers.get('content-type') as string);
        if (!resp.ok) {
            response.set('Cache-Control', 'no-store, max-age=0');
        } else if (resp.headers.get('cache-control')) {
            response.set('Cache-Control', resp.headers.get('cache-control') as string);
        } else if (defaultCacheControl) {
            response.set('Cache-Control', defaultCacheControl);
        }
        if (resp.headers.get('content-length')) response.set('Content-Length', resp.headers.get('content-length') as string);
        if (resp.headers.get('content-disposition')) response.set('Content-Disposition', resp.headers.get('content-disposition') as string);

        if (!resp.body) {
            response.end();
        } else {
            await pipeline(Readable.fromWeb(resp.body as any), response);
        }
    } catch (error) {
        if (response.headersSent) {
            response.end();
            return;
        }
        if (error instanceof SsrfBlockedError) {
            response.status(400).json({ error: error.message });
        } else {
            response.status(500).json({ error: 'Failed to fetch asset' });
        }
    }
}