import { ILeadSource, NormalizedLead, SearchParams, SourceCategory } from "./types";
import { z } from "zod";
import { parseGeoInput, getBBoxFromPoint } from "../utils/geo";

export class OverpassSource implements ILeadSource {
  id = "overpass-osm";
  name = "OpenStreetMap (Overpass)";
  description = "Crowdsourced business data from OpenStreetMap. Free and unauthenticated, perfect for finding local businesses and points of interest.";
  category: SourceCategory = "maps";

  inputSchema = z.object({
    keyword: z.string().min(1, "Search keyword is required"),
    location: z.string().min(1, "Location is required"),
    limit: z.number().optional().default(100),
  });

  async run(params: SearchParams): Promise<NormalizedLead[]> {
    const { keyword, location } = params;
    if (!keyword || !location) return [];

    // Use an identifying User-Agent as per Nominatim/Overpass Usage Policy
    const userAgent = 'LeadForge-App/1.0 (https://github.com/leadforge; support@leadforge.example.com)';
    
    // 1. Check if input is direct coordinates
    const geoInput = parseGeoInput(location);
    let south: number, north: number, west: number, east: number;

    if (geoInput) {
      console.log(`[OSM] Direct coordinate input detected: ${location}`);
      const bbox = getBBoxFromPoint(geoInput.lat, geoInput.lng, geoInput.radius || 2500);
      south = bbox.south;
      north = bbox.north;
      west = bbox.west;
      east = bbox.east;
      console.log(`[OSM] Calculated BBox: ${south},${west},${north},${east}`);
    } else {
      // Geocode location using Nominatim
      console.log(`[OSM] Geocoding location: ${location}`);
      const nomRes = await fetch(`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(location)}&format=json&limit=1`, {
        headers: { 'User-Agent': userAgent }
      });
      
      if (!nomRes.ok) {
        console.error(`[OSM] Nominatim API Error (${nomRes.status})`);
        return [];
      }
      
      const nomData = await nomRes.json();
      if (!nomData || !nomData.length) {
        console.warn('[OSM] Location not found in OpenStreetMap:', location);
        return [];
      }
      
      const bboxArr = nomData[0].boundingbox;
      console.log(`[OSM] Found location: ${nomData[0].display_name}, BBox: ${bboxArr}`);
      
      // bounding box: [lat_min, lat_max, lon_min, lon_max]
      south = parseFloat(bboxArr[0]);
      north = parseFloat(bboxArr[1]);
      west = parseFloat(bboxArr[2]);
      east = parseFloat(bboxArr[3]);
      
      // Performance: If bbox is too tight (less than 0.01), expand it to a reasonable city radius
      if (Math.abs(north - south) < 0.01 || Math.abs(east - west) < 0.01) {
         console.log('[OSM] Bounding box too small, expanding for search...');
         south -= 0.05;
         north += 0.05;
         west -= 0.05;
         east += 0.05;
      }
    }
    
    // Performance: Shrink bbox if too large
    const latSpan = north - south;
    const lonSpan = east - west;
    if (latSpan > 0.5 || lonSpan > 0.5) {
      const centerLat = (north + south) / 2;
      const centerLon = (east + west) / 2;
      south = centerLat - 0.15;
      north = centerLat + 0.15;
      west = centerLon - 0.15;
      east = centerLon + 0.15;
    }

    const cleanKeyword = keyword.trim();
    
    // 2. Query Overpass API
    // Use a more robust mix of exact and fuzzy tag matching to prevent missing obvious categories
    const overpassQuery = `
      [out:json][timeout:25];
      (
        nwr[amenity="${cleanKeyword}"](${south},${west},${north},${east});
        nwr[shop="${cleanKeyword}"](${south},${west},${north},${east});
        nwr[amenity~"${cleanKeyword}",i](${south},${west},${north},${east});
        nwr[shop~"${cleanKeyword}",i](${south},${west},${north},${east});
        nwr[name~"${cleanKeyword}",i](${south},${west},${north},${east});
        nwr[office~"${cleanKeyword}",i](${south},${west},${north},${east});
      );
      out center ${params.limit || 150};
    `;
    
    console.log(`[OSM] Executing Overpass query for: ${cleanKeyword}`);
    
    const endpoints = [
      'https://overpass-api.de/api/interpreter',
      'https://lz4.overpass-api.de/api/interpreter',
      'https://z.overpass-api.de/api/interpreter'
    ];
    
    let osmDataText = '';
    let success = false;
    let lastError = '';

    for (const endpoint of endpoints) {
      try {
        const osmRes = await fetch(endpoint, {
          method: 'POST',
          body: 'data=' + encodeURIComponent(overpassQuery),
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'User-Agent': userAgent
          }
        });
        
        osmDataText = await osmRes.text();
        if (osmRes.ok && !osmDataText.includes('rate_limited') && !osmDataText.includes('remark')) {
          success = true;
          console.log(`[OSM] Successfully fetched from ${endpoint}`);
          break;
        } else {
          lastError = osmDataText.substring(0, 100);
          console.warn(`[OSM] Endpoint ${endpoint} returned: ${lastError}`);
        }
      } catch (error: any) {
        lastError = error.message;
        console.error(`[OSM] Error from ${endpoint}:`, lastError);
      }
    }
    
    if (!success) {
      console.error('[OSM] All Overpass endpoints failed or were rate limited.');
      return [];
    }

    try {
      const osmData = JSON.parse(osmDataText);
      if (!osmData.elements || !osmData.elements.length) {
        console.log('[OSM] No elements found in Overpass response for this area.');
        return [];
      }

      console.log(`[OSM] Found ${osmData.elements.length} elements.`);

      return osmData.elements.map((e: any) => {
        const tags = e.tags || {};
        
        let website = tags.website || tags['contact:website'] || tags.url || '';
        if (website && !website.startsWith('http')) {
          website = 'https://' + website;
        }

        const addressFields = [
            tags['addr:street'] ? `${tags['addr:housenumber'] || ''} ${tags['addr:street']}`.trim() : null,
            tags['addr:city'], 
            tags['addr:state']
        ].filter(Boolean);

        return {
          businessName: tags.name || 'Unnamed Business',
          website: website || undefined,
          email: tags.email || tags['contact:email'] || undefined,
          phone: tags.phone || tags['contact:phone'] || undefined,
          address: addressFields.length > 0 ? addressFields.join(', ') : location,
          city: tags['addr:city'] || location,
          category: tags.amenity || tags.shop || tags.office || keyword,
          confidenceScore: calculateQualityScore(tags, addressFields.length) / 100,
          rawData: e
        };
      });
    } catch (error) {
      console.error('[OSM] Failed to parse Overpass response:', error);
      return [];
    }
  }

  isAvailable(): boolean {
    return true; // Overpass is always "available" as it's free/unauthenticated
  }
}

function calculateQualityScore(tags: any, addressLength: number): number {
  let score = 30;
  if (tags.phone || tags['contact:phone']) score += 20;
  if (tags.email || tags['contact:email']) score += 30;
  if (tags.website || tags['contact:website']) score += 20;
  if (addressLength > 0) score += 15;
  if (tags.opening_hours) score += 10;
  return Math.min(score, 100);
}


