import sdk, {
  ScryptedDeviceBase,
  ScryptedDeviceType,
  DeviceProvider,
  DeviceDiscovery,
  Settings,
  Setting,
  StartStop,
  ScryptedInterface,
  ScryptedNativeId,
  DiscoveredDevice,
  AdoptDevice, // up with other imports.
} from "@scrypted/sdk";

const { deviceManager } = sdk;

///////////////////////////////////////////////////////////////////////////
// RachioZoneDevice
///////////////////////////////////////////////////////////////////////////
export class RachioZoneDevice extends ScryptedDeviceBase implements StartStop {
  public running: boolean = false;
  private plugin: RachioPlugin;
  private zoneId: string;
  private durationSeconds: number;

  constructor(
    plugin: RachioPlugin,
    nativeId: string,
    zoneName: string,
    durationSeconds: number
  ) {
    super(nativeId);
    this.plugin = plugin;
    this.zoneId = nativeId;
    this.durationSeconds = durationSeconds;

    this.console.log(
      `RachioZoneDevice created: ${zoneName} (zoneId=${this.zoneId}), default watering duration=${durationSeconds}s`
    );
  }

  // StartStop interface property from Scrypted is "running"
  // We won't redefine get/set 'running' to avoid conflicts.
  // Instead, we simply set this.binaryState = true/false as needed.

  async start() {
    try {
      this.console.log(
        `Starting zone ${this.zoneId} for ${this.durationSeconds} seconds.`
      );

      const url = "https://api.rach.io/1/public/zone/start";
      const body = {
        id: this.zoneId,
        duration: this.durationSeconds,
      };

      await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${this.plugin.rachioAuthToken}`,
        },
        body: JSON.stringify(body),
      });

      // Mark the zone as running
      this.running = true;
      this.console.log(`Zone ${this.zoneId} started.`);
    } catch (e) {
      this.console.error(`Error starting zone ${this.zoneId}: ${e}`);
    }
  }

  async stop() {
    // Rachio does not offer "stop single zone by zoneID";
    // Instead "stop_water" stops all running zones on the device
    try {
      this.console.log(`Stopping watering for all zones (including ${this.zoneId}).`);

      const url = "https://api.rach.io/1/public/device/stop_water";
      await fetch(url, {
        method: "PUT",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${this.plugin.rachioAuthToken}`,
        },
      });

      // Mark the zone as no longer running
      this.running = false;
      this.console.log(`Zone ${this.zoneId} stopped.`);
    } catch (e) {
      this.console.error(`Error stopping zone ${this.zoneId}: ${e}`);
    }
  }
}

///////////////////////////////////////////////////////////////////////////
// RachioPlugin - main plugin
///////////////////////////////////////////////////////////////////////////
export class RachioPlugin extends ScryptedDeviceBase
  implements DeviceProvider, DeviceDiscovery, Settings
{
  // Keep a local map of discovered zones
  devices = new Map<string, RachioZoneDevice>();

  // Rachio Auth Token stored in plugin settings
  get rachioAuthToken(): string {
    return this.storage.getItem("rachioAuthToken") || "";
  }
  set rachioAuthToken(token: string) {
    this.storage.setItem("rachioAuthToken", token);
  }

  constructor(nativeId?: ScryptedNativeId) {
    super(nativeId);

    // If there's already a token, try discovering on startup
    if (this.rachioAuthToken) {
      // "scan" parameter is optional, so pass 'false' or 'true' or nothing
      this.discoverDevices(false);
    }
  }

  async adoptDevice(device: AdoptDevice): Promise<string> {
    this.console.log('adoptDevice not implemented.');
    // Return a valid nativeId string.
    return device.nativeId || '';
  }

  async releaseDevice(id: ScryptedNativeId): Promise<void> {
    this.console.log('releaseDevice not implemented.');
  }

  /************************************************************************
   * Scrypted Settings
   ************************************************************************/
  async getSettings(): Promise<Setting[]> {
    return [
      {
        key: "rachioAuthToken",
        title: "Rachio API Token",
        description: "Enter your Rachio Personal Access Token here",
        value: this.rachioAuthToken || "",
      },
    ];
  }

  async putSetting(key: string, value: string) {
    if (key === "rachioAuthToken") {
      this.rachioAuthToken = value.trim();
      this.console.log(`Rachio API token updated. Discovering devices...`);
      // re-discover after token changes
      await this.discoverDevices(false);
    }
  }

  /************************************************************************
   * DeviceProvider
   ************************************************************************/
  // Must be async and return a Promise
  async getDevice(nativeId: ScryptedNativeId) {
    return nativeId ? this.devices.get(nativeId) : undefined;
  }

  /************************************************************************
   * DeviceDiscovery
   ************************************************************************/
  // Must return a Promise of DiscoveredDevice[] and accept an optional boolean
  async discoverDevices(scan?: boolean): Promise<DiscoveredDevice[]> {
    this.console.log(`Discovering Rachio devices. scan=${scan ?? false}`);
    const discoveredDevices: DiscoveredDevice[] = [];

    if (!this.rachioAuthToken) {
      this.console.warn("No Rachio auth token set, cannot discover devices.");
      return discoveredDevices; // empty array
    }

    try {
      // Step 1: Get Person info to retrieve userId
      let url = "https://api.rach.io/1/public/person/info";
      let resp = await fetch(url, {
        method: "GET",
        headers: {
          Authorization: `Bearer ${this.rachioAuthToken}`,
        },
      });
      if (!resp.ok) {
        throw new Error(`Failed fetching /person/info: ${resp.statusText}`);
      }
      const infoData = await resp.json();
      const personId = infoData.id;

      // Step 2: With personId, fetch /person/:id to get devices
      url = `https://api.rach.io/1/public/person/${personId}`;
      resp = await fetch(url, {
        method: "GET",
        headers: {
          Authorization: `Bearer ${this.rachioAuthToken}`,
        },
      });
      if (!resp.ok) {
        throw new Error(`Failed fetching /person/${personId}: ${resp.statusText}`);
      }
      const personDetail = await resp.json();

      if (!personDetail.devices || !Array.isArray(personDetail.devices)) {
        this.console.warn("No Rachio devices found in this account.");
        return discoveredDevices;
      }

      // Each device can have multiple "zones"
      for (const device of personDetail.devices) {
        const deviceName = device.name;
        const deviceId = device.id;
        this.console.log(`Found Rachio controller: ${deviceName}, id=${deviceId}`);

        const zones = device.zones || [];
        for (const zone of zones) {
          if (!zone.enabled) {
            this.console.log(`Skipping disabled zone: ${zone.name} (id=${zone.id})`);
            continue;
          }
          const zoneId = zone.id as string;
          const zoneName = zone.name as string;

          // Build a discovered device descriptor
          const discoveredDevice: DiscoveredDevice = {
            name: zoneName,
            nativeId: zoneId,
            description: `Rachio Zone: ${zoneName}`,
            type: ScryptedDeviceType.Valve,
            interfaces: [ScryptedInterface.StartStop],
            info: {
              manufacturer: "Rachio",
              model: "Rachio 3",
              firmware: device.firmwareVersion || "",
              serialNumber: zoneId,
            },
          };
          discoveredDevices.push(discoveredDevice);
        }
      }

      // Inform Scrypted about the discovered devices
      deviceManager.onDevicesChanged({
        devices: discoveredDevices.map(d => ({
          nativeId: d.nativeId!,
          name: d.name,
          description: d.description,
          type: d.type,
          interfaces: d.interfaces || [],
          info: d.info,
        })),
      });

      // Create or update local device wrappers
      for (const d of discoveredDevices) {
        if (!d.nativeId) {
          this.console.error(`Discovered device has no nativeId, skipping: ${d.name}`);
          continue;
        }

        const sid = d.nativeId; // safe string
        let existing = this.devices.get(sid);
        if (!existing) {
          existing = new RachioZoneDevice(this, sid, d.name ?? 'Unknown Zone', 60);
          this.devices.set(sid, existing);
        }
      }
      this.console.log("Rachio device discovery complete.");
    } catch (e) {
      this.console.error(`Rachio discovery error: ${e}`);
    }

    // Return the discovered devices
    return discoveredDevices;
  }
}

// Default export for Scrypted
export default RachioPlugin;